diff --git a/800OTClient/LICENSE b/800OTClient/LICENSE new file mode 100644 index 0000000..509b9b2 --- /dev/null +++ b/800OTClient/LICENSE @@ -0,0 +1,22 @@ +OTClientV8 is made available under the MIT License + +Copyright (c) 2010-2017 OTClient +Copyright (c) 2018-2021 OTClientV8 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/800OTClient/README.md b/800OTClient/README.md new file mode 100644 index 0000000..acbd7bc --- /dev/null +++ b/800OTClient/README.md @@ -0,0 +1,99 @@ +# OTClientV8 + +OTClientV8 is highly optimized, cross-platform tile based 2d game engine built with c++17, lua, physfs, OpenGL ES 2.0 and OpenAL. +It has been created as alternative client for game called [Tibia](https://tibia.com/), but now it's much more functional and powerful. +It works well even on 12 years old computers. In April 2021 it reached 290k unique installations, including 80k android installations. + +Supported platforms: +- Windows (min. Windows 7, requires https://aka.ms/vs/16/release/vc_redist.x86.exe) +- Android (min. 5.0) +- Linux +- Mac Os (requires https://www.xquartz.org/) + +### Forum: http://otclient.net +### Discord: https://discord.gg/feySup6 +### Website: http://otclient.ovh +### Wiki: https://github.com/OTCv8/otclientv8/wiki + +## Version for developers + +In this repository, you can find clean, always up-to-date, ready to use version of OTClientv8. Most commits from version 3.0 are automatic using GitHub Actions. If you want to help with development, please visit repository for developers - https://github.com/OTCv8/otcv8-dev + +## FEATURES +- Rewritten and optimized rendering (60 fps on 11 years old computer) +- Better DirectX9 and DirectX11 support +- Adaptive rendering (automated graphics optimizations) +- Rewritten and optimized light rendering +- Rewritten path finding and auto walking +- Rewritten walking system with animations +- HTTP/HTTPS lua API with JSON support +- WebSocket lua API +- Auto updater with failsafe (recovery) mode +- New filesystem +- File encryption and compression +- Automatic diagnostic system +- Refreshed interface +- New crash and error handler +- New HTTP login protocol +- Ingame shop +- Updated hotkey manager +- Updated and optimized battle list +- Crosshair, floor fading, extra health/mana bars and panels +- Much more client options +- Removed a lot of useless and outdated things +- Advanced bot +- Linux version +- Full tibia 11.00 support +- Layouts +- New login server (with ingame account and character creation) +- Support for proxies to lower latency and protect against DDoS (extra paid option) +- Bot protection (extra paid option) + +### And hundreds of smaller features, optimizations and bug fixes! +### Check out [Wiki page](https://github.com/OTCv8/otclientv8/wiki) to see how activate and use new features + +### Old tools, like updater and tutorials has been moved to: [OTCv8/otcv8-tools](https://github.com/OTCv8/otcv8-tools) +### There's github repo of tfs 1.3 with otclientv8 features: [OTCv8/otclientv8-tfs](https://github.com/OTCv8/forgottenserver) + +## Quick Start for players + +Download whole repository and run one of binary file. + +## Quick Start for server owners + +Open `init.lua` and edit: + +``` +-- CONFIG +APP_NAME = "otclientv8" -- important, change it, it's name for config dir and files in appdata +APP_VERSION = 1337 -- client version for updater and login to indentify outdated client +DEFAULT_LAYOUT = "retro" + +-- If you don't use updater or other service, set it to updater = "" +Services = { + website = "http://otclient.ovh", -- currently not used + updater = "http://otclient.ovh/api/updater.php", + news = "http://otclient.ovh/api/news.php", + stats = "", + crash = "http://otclient.ovh/api/crash.php", + feedback = "http://otclient.ovh/api/feedback.php" +} + +-- Servers accept http login url or ip:port:version +Servers = { + OTClientV8 = "http://otclient.ovh/api/login.php", + OTClientV8proxy = "http://otclient.ovh/api/login.php?proxy=1", + OTClientV8classic = "otclient.ovh:7171:1099", + OTClientV8cwithfeatures = "otclient.ovh:7171:1099:25:30:80:90", +} +ALLOW_CUSTOM_SERVERS = true -- if true it will show option ANOTHER on server list +-- CONFIG END +``` + +Also remember to add your sprite and data file to data/things + +That's it, you're ready to use OTClientV8. + +DirectX version requires 3 dlls: libEGL.dll libGLESv2.dll d3dcompiler_47.dll + +If it can't start (missing dlls) then user need to install visual studio 2019 redistributable x86: https://aka.ms/vs/16/release/vc_redist.x86.exe diff --git a/800OTClient/data/cursors/cursors.otml b/800OTClient/data/cursors/cursors.otml new file mode 100644 index 0000000..c4f17f1 --- /dev/null +++ b/800OTClient/data/cursors/cursors.otml @@ -0,0 +1,16 @@ +Cursors + text: + image: textcursor + hot-spot: 4 9 + target: + image: targetcursor + hot-spot: 9 9 + horizontal: + image: horizontalcursor + hot-spot: 9 4 + vertical: + image: verticalcursor + hot-spot: 4 9 + pointer: + image: pointer + hot-spot: 5 0 diff --git a/800OTClient/data/cursors/horizontalcursor.png b/800OTClient/data/cursors/horizontalcursor.png new file mode 100644 index 0000000..6ba0f27 Binary files /dev/null and b/800OTClient/data/cursors/horizontalcursor.png differ diff --git a/800OTClient/data/cursors/pointer.png b/800OTClient/data/cursors/pointer.png new file mode 100644 index 0000000..e41785e Binary files /dev/null and b/800OTClient/data/cursors/pointer.png differ diff --git a/800OTClient/data/cursors/targetcursor.png b/800OTClient/data/cursors/targetcursor.png new file mode 100644 index 0000000..3ce607b Binary files /dev/null and b/800OTClient/data/cursors/targetcursor.png differ diff --git a/800OTClient/data/cursors/textcursor.png b/800OTClient/data/cursors/textcursor.png new file mode 100644 index 0000000..abcd823 Binary files /dev/null and b/800OTClient/data/cursors/textcursor.png differ diff --git a/800OTClient/data/cursors/verticalcursor.png b/800OTClient/data/cursors/verticalcursor.png new file mode 100644 index 0000000..1f31d1c Binary files /dev/null and b/800OTClient/data/cursors/verticalcursor.png differ diff --git a/800OTClient/data/fonts/cipsoftFont.otfont b/800OTClient/data/fonts/cipsoftFont.otfont new file mode 100644 index 0000000..14de8dd --- /dev/null +++ b/800OTClient/data/fonts/cipsoftFont.otfont @@ -0,0 +1,6 @@ +Font + name: cipsoftFont + texture: cipsoftFont + height: 8 + glyph-size: 8 8 + space-width: 2 diff --git a/800OTClient/data/fonts/cipsoftFont.png b/800OTClient/data/fonts/cipsoftFont.png new file mode 100644 index 0000000..65efe25 Binary files /dev/null and b/800OTClient/data/fonts/cipsoftFont.png differ diff --git a/800OTClient/data/fonts/sans-bold-16px.otfont b/800OTClient/data/fonts/sans-bold-16px.otfont new file mode 100644 index 0000000..bdf5cf4 --- /dev/null +++ b/800OTClient/data/fonts/sans-bold-16px.otfont @@ -0,0 +1,6 @@ +Font + name: sans-bold-16px + texture: sans-bold-16px_cp1252 + height: 20 + glyph-size: 24 24 + space-width: 3 diff --git a/800OTClient/data/fonts/sans-bold-16px_cp1252.png b/800OTClient/data/fonts/sans-bold-16px_cp1252.png new file mode 100644 index 0000000..eef9474 Binary files /dev/null and b/800OTClient/data/fonts/sans-bold-16px_cp1252.png differ diff --git a/800OTClient/data/fonts/small-9px.otfont b/800OTClient/data/fonts/small-9px.otfont new file mode 100644 index 0000000..12c83bf --- /dev/null +++ b/800OTClient/data/fonts/small-9px.otfont @@ -0,0 +1,7 @@ +Font + name: small-9px + texture: small-9px + height: 9 + glyph-size: 9 9 + space-width: 3 + spacing: 1 0 diff --git a/800OTClient/data/fonts/small-9px.png b/800OTClient/data/fonts/small-9px.png new file mode 100644 index 0000000..37643a1 Binary files /dev/null and b/800OTClient/data/fonts/small-9px.png differ diff --git a/800OTClient/data/fonts/terminus-10px.otfont b/800OTClient/data/fonts/terminus-10px.otfont new file mode 100644 index 0000000..957da0d --- /dev/null +++ b/800OTClient/data/fonts/terminus-10px.otfont @@ -0,0 +1,8 @@ +Font + name: terminus-10px + texture: terminus-10px + height: 12 + y-offset: 0 + glyph-size: 16 16 + fixed-glyph-width: 6 + space-width: 6 diff --git a/800OTClient/data/fonts/terminus-10px.png b/800OTClient/data/fonts/terminus-10px.png new file mode 100644 index 0000000..4e8f500 Binary files /dev/null and b/800OTClient/data/fonts/terminus-10px.png differ diff --git a/800OTClient/data/fonts/terminus-14px-bold.otfont b/800OTClient/data/fonts/terminus-14px-bold.otfont new file mode 100644 index 0000000..0814f4a --- /dev/null +++ b/800OTClient/data/fonts/terminus-14px-bold.otfont @@ -0,0 +1,8 @@ +Font + name: terminus-14px-bold + texture: terminus-14px-bold + height: 16 + y-offset: 2 + glyph-size: 16 16 + fixed-glyph-width: 8 + space-width: 8 diff --git a/800OTClient/data/fonts/terminus-14px-bold.png b/800OTClient/data/fonts/terminus-14px-bold.png new file mode 100644 index 0000000..99962a5 Binary files /dev/null and b/800OTClient/data/fonts/terminus-14px-bold.png differ diff --git a/800OTClient/data/fonts/verdana-11px-antialised.otfont b/800OTClient/data/fonts/verdana-11px-antialised.otfont new file mode 100644 index 0000000..c6cac89 --- /dev/null +++ b/800OTClient/data/fonts/verdana-11px-antialised.otfont @@ -0,0 +1,7 @@ +Font + name: verdana-11px-antialised + texture: verdana-11px-antialised_cp1252 + height: 14 + glyph-size: 16 16 + space-width: 4 + default: true diff --git a/800OTClient/data/fonts/verdana-11px-antialised_cp1250.png b/800OTClient/data/fonts/verdana-11px-antialised_cp1250.png new file mode 100644 index 0000000..ba9e6cb Binary files /dev/null and b/800OTClient/data/fonts/verdana-11px-antialised_cp1250.png differ diff --git a/800OTClient/data/fonts/verdana-11px-antialised_cp1252.png b/800OTClient/data/fonts/verdana-11px-antialised_cp1252.png new file mode 100644 index 0000000..c01e218 Binary files /dev/null and b/800OTClient/data/fonts/verdana-11px-antialised_cp1252.png differ diff --git a/800OTClient/data/fonts/verdana-11px-monochrome.otfont b/800OTClient/data/fonts/verdana-11px-monochrome.otfont new file mode 100644 index 0000000..e39cf0b --- /dev/null +++ b/800OTClient/data/fonts/verdana-11px-monochrome.otfont @@ -0,0 +1,6 @@ +Font + name: verdana-11px-monochrome + texture: verdana-11px-monochrome_cp1252 + height: 14 + glyph-size: 16 16 + space-width: 3 diff --git a/800OTClient/data/fonts/verdana-11px-monochrome_cp1250.png b/800OTClient/data/fonts/verdana-11px-monochrome_cp1250.png new file mode 100644 index 0000000..11002ef Binary files /dev/null and b/800OTClient/data/fonts/verdana-11px-monochrome_cp1250.png differ diff --git a/800OTClient/data/fonts/verdana-11px-monochrome_cp1252.png b/800OTClient/data/fonts/verdana-11px-monochrome_cp1252.png new file mode 100644 index 0000000..d053ca5 Binary files /dev/null and b/800OTClient/data/fonts/verdana-11px-monochrome_cp1252.png differ diff --git a/800OTClient/data/fonts/verdana-11px-rounded.otfont b/800OTClient/data/fonts/verdana-11px-rounded.otfont new file mode 100644 index 0000000..9c18e23 --- /dev/null +++ b/800OTClient/data/fonts/verdana-11px-rounded.otfont @@ -0,0 +1,8 @@ +Font + name: verdana-11px-rounded + texture: verdana-11px-rounded_cp1252 + height: 16 + glyph-size: 16 16 + y-offset: -2 + spacing: -1 -3 + space-width: 4 diff --git a/800OTClient/data/fonts/verdana-11px-rounded_cp1250.png b/800OTClient/data/fonts/verdana-11px-rounded_cp1250.png new file mode 100644 index 0000000..bcf4638 Binary files /dev/null and b/800OTClient/data/fonts/verdana-11px-rounded_cp1250.png differ diff --git a/800OTClient/data/fonts/verdana-11px-rounded_cp1252.png b/800OTClient/data/fonts/verdana-11px-rounded_cp1252.png new file mode 100644 index 0000000..0ff0322 Binary files /dev/null and b/800OTClient/data/fonts/verdana-11px-rounded_cp1252.png differ diff --git a/800OTClient/data/fonts/verdana-9px-bold.otfont b/800OTClient/data/fonts/verdana-9px-bold.otfont new file mode 100644 index 0000000..beaf9ba --- /dev/null +++ b/800OTClient/data/fonts/verdana-9px-bold.otfont @@ -0,0 +1,8 @@ +Font + name: verdana-9px-bold + texture: verdana-9px-bold + height: 12 + glyph-size: 13 13 + space-width: 4 + spacing: 0 0 + diff --git a/800OTClient/data/fonts/verdana-9px-bold.png b/800OTClient/data/fonts/verdana-9px-bold.png new file mode 100644 index 0000000..51ccd06 Binary files /dev/null and b/800OTClient/data/fonts/verdana-9px-bold.png differ diff --git a/800OTClient/data/fonts/verdana-9px-italic.otfont b/800OTClient/data/fonts/verdana-9px-italic.otfont new file mode 100644 index 0000000..5e32e32 --- /dev/null +++ b/800OTClient/data/fonts/verdana-9px-italic.otfont @@ -0,0 +1,6 @@ +Font + name: verdana-9px-italic + texture: verdana-9px-italic + height: 12 + glyph-size: 13 13 + space-width: 3 diff --git a/800OTClient/data/fonts/verdana-9px-italic.png b/800OTClient/data/fonts/verdana-9px-italic.png new file mode 100644 index 0000000..b3646e1 Binary files /dev/null and b/800OTClient/data/fonts/verdana-9px-italic.png differ diff --git a/800OTClient/data/fonts/verdana-9px.otfont b/800OTClient/data/fonts/verdana-9px.otfont new file mode 100644 index 0000000..f899759 --- /dev/null +++ b/800OTClient/data/fonts/verdana-9px.otfont @@ -0,0 +1,7 @@ +Font + name: verdana-9px + texture: verdana-9px + height: 13 + glyph-size: 13 13 + space-width: 3 + spacing: 0 -4 diff --git a/800OTClient/data/fonts/verdana-9px.png b/800OTClient/data/fonts/verdana-9px.png new file mode 100644 index 0000000..9f566ce Binary files /dev/null and b/800OTClient/data/fonts/verdana-9px.png differ diff --git a/800OTClient/data/images/background.png b/800OTClient/data/images/background.png new file mode 100644 index 0000000..042269e Binary files /dev/null and b/800OTClient/data/images/background.png differ diff --git a/800OTClient/data/images/bars/health1.png b/800OTClient/data/images/bars/health1.png new file mode 100644 index 0000000..41579ba Binary files /dev/null and b/800OTClient/data/images/bars/health1.png differ diff --git a/800OTClient/data/images/bars/mana1.png b/800OTClient/data/images/bars/mana1.png new file mode 100644 index 0000000..c635dd5 Binary files /dev/null and b/800OTClient/data/images/bars/mana1.png differ diff --git a/800OTClient/data/images/clienticon.png b/800OTClient/data/images/clienticon.png new file mode 100644 index 0000000..4200fb5 Binary files /dev/null and b/800OTClient/data/images/clienticon.png differ diff --git a/800OTClient/data/images/crosshair/default.png b/800OTClient/data/images/crosshair/default.png new file mode 100644 index 0000000..1715d95 Binary files /dev/null and b/800OTClient/data/images/crosshair/default.png differ diff --git a/800OTClient/data/images/crosshair/full.png b/800OTClient/data/images/crosshair/full.png new file mode 100644 index 0000000..b5c2173 Binary files /dev/null and b/800OTClient/data/images/crosshair/full.png differ diff --git a/800OTClient/data/images/flags/de.png b/800OTClient/data/images/flags/de.png new file mode 100644 index 0000000..103b04d Binary files /dev/null and b/800OTClient/data/images/flags/de.png differ diff --git a/800OTClient/data/images/flags/en.png b/800OTClient/data/images/flags/en.png new file mode 100644 index 0000000..f36a688 Binary files /dev/null and b/800OTClient/data/images/flags/en.png differ diff --git a/800OTClient/data/images/flags/es.png b/800OTClient/data/images/flags/es.png new file mode 100644 index 0000000..fda8ccc Binary files /dev/null and b/800OTClient/data/images/flags/es.png differ diff --git a/800OTClient/data/images/flags/pl.png b/800OTClient/data/images/flags/pl.png new file mode 100644 index 0000000..52795d0 Binary files /dev/null and b/800OTClient/data/images/flags/pl.png differ diff --git a/800OTClient/data/images/flags/pt.png b/800OTClient/data/images/flags/pt.png new file mode 100644 index 0000000..b0c7a22 Binary files /dev/null and b/800OTClient/data/images/flags/pt.png differ diff --git a/800OTClient/data/images/flags/sv.png b/800OTClient/data/images/flags/sv.png new file mode 100644 index 0000000..60932e2 Binary files /dev/null and b/800OTClient/data/images/flags/sv.png differ diff --git a/800OTClient/data/images/game/actionbarslot.png b/800OTClient/data/images/game/actionbarslot.png new file mode 100644 index 0000000..448b71e Binary files /dev/null and b/800OTClient/data/images/game/actionbarslot.png differ diff --git a/800OTClient/data/images/game/battle/battle_monsters.png b/800OTClient/data/images/game/battle/battle_monsters.png new file mode 100644 index 0000000..9c01361 Binary files /dev/null and b/800OTClient/data/images/game/battle/battle_monsters.png differ diff --git a/800OTClient/data/images/game/battle/battle_npcs.png b/800OTClient/data/images/game/battle/battle_npcs.png new file mode 100644 index 0000000..d19635d Binary files /dev/null and b/800OTClient/data/images/game/battle/battle_npcs.png differ diff --git a/800OTClient/data/images/game/battle/battle_party.png b/800OTClient/data/images/game/battle/battle_party.png new file mode 100644 index 0000000..77fd67e Binary files /dev/null and b/800OTClient/data/images/game/battle/battle_party.png differ diff --git a/800OTClient/data/images/game/battle/battle_players.png b/800OTClient/data/images/game/battle/battle_players.png new file mode 100644 index 0000000..84c74b9 Binary files /dev/null and b/800OTClient/data/images/game/battle/battle_players.png differ diff --git a/800OTClient/data/images/game/battle/battle_skulls.png b/800OTClient/data/images/game/battle/battle_skulls.png new file mode 100644 index 0000000..3f3a2ec Binary files /dev/null and b/800OTClient/data/images/game/battle/battle_skulls.png differ diff --git a/800OTClient/data/images/game/circle/left_empty.png b/800OTClient/data/images/game/circle/left_empty.png new file mode 100644 index 0000000..7868b9f Binary files /dev/null and b/800OTClient/data/images/game/circle/left_empty.png differ diff --git a/800OTClient/data/images/game/circle/left_full.png b/800OTClient/data/images/game/circle/left_full.png new file mode 100644 index 0000000..dd9ecf4 Binary files /dev/null and b/800OTClient/data/images/game/circle/left_full.png differ diff --git a/800OTClient/data/images/game/circle/right_empty.png b/800OTClient/data/images/game/circle/right_empty.png new file mode 100644 index 0000000..a20de6a Binary files /dev/null and b/800OTClient/data/images/game/circle/right_empty.png differ diff --git a/800OTClient/data/images/game/circle/right_full.png b/800OTClient/data/images/game/circle/right_full.png new file mode 100644 index 0000000..8d866d2 Binary files /dev/null and b/800OTClient/data/images/game/circle/right_full.png differ diff --git a/800OTClient/data/images/game/combatmodes/chasemode.png b/800OTClient/data/images/game/combatmodes/chasemode.png new file mode 100644 index 0000000..f3ef705 Binary files /dev/null and b/800OTClient/data/images/game/combatmodes/chasemode.png differ diff --git a/800OTClient/data/images/game/combatmodes/fightbalanced.png b/800OTClient/data/images/game/combatmodes/fightbalanced.png new file mode 100644 index 0000000..3113538 Binary files /dev/null and b/800OTClient/data/images/game/combatmodes/fightbalanced.png differ diff --git a/800OTClient/data/images/game/combatmodes/fightdefensive.png b/800OTClient/data/images/game/combatmodes/fightdefensive.png new file mode 100644 index 0000000..3829a21 Binary files /dev/null and b/800OTClient/data/images/game/combatmodes/fightdefensive.png differ diff --git a/800OTClient/data/images/game/combatmodes/fightoffensive.png b/800OTClient/data/images/game/combatmodes/fightoffensive.png new file mode 100644 index 0000000..2fb6e79 Binary files /dev/null and b/800OTClient/data/images/game/combatmodes/fightoffensive.png differ diff --git a/800OTClient/data/images/game/combatmodes/mount.png b/800OTClient/data/images/game/combatmodes/mount.png new file mode 100644 index 0000000..879646a Binary files /dev/null and b/800OTClient/data/images/game/combatmodes/mount.png differ diff --git a/800OTClient/data/images/game/combatmodes/pvp.png b/800OTClient/data/images/game/combatmodes/pvp.png new file mode 100644 index 0000000..b3087c0 Binary files /dev/null and b/800OTClient/data/images/game/combatmodes/pvp.png differ diff --git a/800OTClient/data/images/game/combatmodes/redfistmode.png b/800OTClient/data/images/game/combatmodes/redfistmode.png new file mode 100644 index 0000000..e197210 Binary files /dev/null and b/800OTClient/data/images/game/combatmodes/redfistmode.png differ diff --git a/800OTClient/data/images/game/combatmodes/safefight.png b/800OTClient/data/images/game/combatmodes/safefight.png new file mode 100644 index 0000000..2117067 Binary files /dev/null and b/800OTClient/data/images/game/combatmodes/safefight.png differ diff --git a/800OTClient/data/images/game/combatmodes/whitedovemode.png b/800OTClient/data/images/game/combatmodes/whitedovemode.png new file mode 100644 index 0000000..29d4e0c Binary files /dev/null and b/800OTClient/data/images/game/combatmodes/whitedovemode.png differ diff --git a/800OTClient/data/images/game/combatmodes/whitehandmode.png b/800OTClient/data/images/game/combatmodes/whitehandmode.png new file mode 100644 index 0000000..c010d82 Binary files /dev/null and b/800OTClient/data/images/game/combatmodes/whitehandmode.png differ diff --git a/800OTClient/data/images/game/combatmodes/yellowhandmode.png b/800OTClient/data/images/game/combatmodes/yellowhandmode.png new file mode 100644 index 0000000..13a0b02 Binary files /dev/null and b/800OTClient/data/images/game/combatmodes/yellowhandmode.png differ diff --git a/800OTClient/data/images/game/console/channels.png b/800OTClient/data/images/game/console/channels.png new file mode 100644 index 0000000..885ba22 Binary files /dev/null and b/800OTClient/data/images/game/console/channels.png differ diff --git a/800OTClient/data/images/game/console/clearchannel.png b/800OTClient/data/images/game/console/clearchannel.png new file mode 100644 index 0000000..201bd82 Binary files /dev/null and b/800OTClient/data/images/game/console/clearchannel.png differ diff --git a/800OTClient/data/images/game/console/closechannel.png b/800OTClient/data/images/game/console/closechannel.png new file mode 100644 index 0000000..0b130d4 Binary files /dev/null and b/800OTClient/data/images/game/console/closechannel.png differ diff --git a/800OTClient/data/images/game/console/downarrow.png b/800OTClient/data/images/game/console/downarrow.png new file mode 100644 index 0000000..d3a3aa4 Binary files /dev/null and b/800OTClient/data/images/game/console/downarrow.png differ diff --git a/800OTClient/data/images/game/console/ignore.png b/800OTClient/data/images/game/console/ignore.png new file mode 100644 index 0000000..8ed2df6 Binary files /dev/null and b/800OTClient/data/images/game/console/ignore.png differ diff --git a/800OTClient/data/images/game/console/leftarrow.png b/800OTClient/data/images/game/console/leftarrow.png new file mode 100644 index 0000000..7e065f5 Binary files /dev/null and b/800OTClient/data/images/game/console/leftarrow.png differ diff --git a/800OTClient/data/images/game/console/rightarrow.png b/800OTClient/data/images/game/console/rightarrow.png new file mode 100644 index 0000000..4c51e9f Binary files /dev/null and b/800OTClient/data/images/game/console/rightarrow.png differ diff --git a/800OTClient/data/images/game/console/say.png b/800OTClient/data/images/game/console/say.png new file mode 100644 index 0000000..82ffb5f Binary files /dev/null and b/800OTClient/data/images/game/console/say.png differ diff --git a/800OTClient/data/images/game/console/uparrow.png b/800OTClient/data/images/game/console/uparrow.png new file mode 100644 index 0000000..83153ac Binary files /dev/null and b/800OTClient/data/images/game/console/uparrow.png differ diff --git a/800OTClient/data/images/game/console/whisper.png b/800OTClient/data/images/game/console/whisper.png new file mode 100644 index 0000000..440ac16 Binary files /dev/null and b/800OTClient/data/images/game/console/whisper.png differ diff --git a/800OTClient/data/images/game/console/yell.png b/800OTClient/data/images/game/console/yell.png new file mode 100644 index 0000000..398bc28 Binary files /dev/null and b/800OTClient/data/images/game/console/yell.png differ diff --git a/800OTClient/data/images/game/creaturetype/summon_other.png b/800OTClient/data/images/game/creaturetype/summon_other.png new file mode 100644 index 0000000..6f6fa60 Binary files /dev/null and b/800OTClient/data/images/game/creaturetype/summon_other.png differ diff --git a/800OTClient/data/images/game/creaturetype/summon_own.png b/800OTClient/data/images/game/creaturetype/summon_own.png new file mode 100644 index 0000000..0abcb94 Binary files /dev/null and b/800OTClient/data/images/game/creaturetype/summon_own.png differ diff --git a/800OTClient/data/images/game/dangerous.png b/800OTClient/data/images/game/dangerous.png new file mode 100644 index 0000000..c927c67 Binary files /dev/null and b/800OTClient/data/images/game/dangerous.png differ diff --git a/800OTClient/data/images/game/emblems/emblem_blue.png b/800OTClient/data/images/game/emblems/emblem_blue.png new file mode 100644 index 0000000..a018e3d Binary files /dev/null and b/800OTClient/data/images/game/emblems/emblem_blue.png differ diff --git a/800OTClient/data/images/game/emblems/emblem_green.png b/800OTClient/data/images/game/emblems/emblem_green.png new file mode 100644 index 0000000..e5ead37 Binary files /dev/null and b/800OTClient/data/images/game/emblems/emblem_green.png differ diff --git a/800OTClient/data/images/game/emblems/emblem_member.png b/800OTClient/data/images/game/emblems/emblem_member.png new file mode 100644 index 0000000..7af0ad9 Binary files /dev/null and b/800OTClient/data/images/game/emblems/emblem_member.png differ diff --git a/800OTClient/data/images/game/emblems/emblem_other.png b/800OTClient/data/images/game/emblems/emblem_other.png new file mode 100644 index 0000000..2b2d5ad Binary files /dev/null and b/800OTClient/data/images/game/emblems/emblem_other.png differ diff --git a/800OTClient/data/images/game/emblems/emblem_red.png b/800OTClient/data/images/game/emblems/emblem_red.png new file mode 100644 index 0000000..94d712a Binary files /dev/null and b/800OTClient/data/images/game/emblems/emblem_red.png differ diff --git a/800OTClient/data/images/game/imbuing/100percent.png b/800OTClient/data/images/game/imbuing/100percent.png new file mode 100644 index 0000000..9d26eb5 Binary files /dev/null and b/800OTClient/data/images/game/imbuing/100percent.png differ diff --git a/800OTClient/data/images/game/imbuing/clear.png b/800OTClient/data/images/game/imbuing/clear.png new file mode 100644 index 0000000..f18e667 Binary files /dev/null and b/800OTClient/data/images/game/imbuing/clear.png differ diff --git a/800OTClient/data/images/game/imbuing/imbue_empty.png b/800OTClient/data/images/game/imbuing/imbue_empty.png new file mode 100644 index 0000000..641f14d Binary files /dev/null and b/800OTClient/data/images/game/imbuing/imbue_empty.png differ diff --git a/800OTClient/data/images/game/imbuing/imbue_green.png b/800OTClient/data/images/game/imbuing/imbue_green.png new file mode 100644 index 0000000..19e4a92 Binary files /dev/null and b/800OTClient/data/images/game/imbuing/imbue_green.png differ diff --git a/800OTClient/data/images/game/imbuing/slot.png b/800OTClient/data/images/game/imbuing/slot.png new file mode 100644 index 0000000..d2b1e05 Binary files /dev/null and b/800OTClient/data/images/game/imbuing/slot.png differ diff --git a/800OTClient/data/images/game/imbuing/slot_disabled.png b/800OTClient/data/images/game/imbuing/slot_disabled.png new file mode 100644 index 0000000..5ac8e13 Binary files /dev/null and b/800OTClient/data/images/game/imbuing/slot_disabled.png differ diff --git a/800OTClient/data/images/game/imbuing/slot_inactive.png b/800OTClient/data/images/game/imbuing/slot_inactive.png new file mode 100644 index 0000000..11275ce Binary files /dev/null and b/800OTClient/data/images/game/imbuing/slot_inactive.png differ diff --git a/800OTClient/data/images/game/minimap/cross.png b/800OTClient/data/images/game/minimap/cross.png new file mode 100644 index 0000000..fa8a7ad Binary files /dev/null and b/800OTClient/data/images/game/minimap/cross.png differ diff --git a/800OTClient/data/images/game/minimap/flag0.png b/800OTClient/data/images/game/minimap/flag0.png new file mode 100644 index 0000000..1b80e29 Binary files /dev/null and b/800OTClient/data/images/game/minimap/flag0.png differ diff --git a/800OTClient/data/images/game/minimap/flag1.png b/800OTClient/data/images/game/minimap/flag1.png new file mode 100644 index 0000000..560bf79 Binary files /dev/null and b/800OTClient/data/images/game/minimap/flag1.png differ diff --git a/800OTClient/data/images/game/minimap/flag10.png b/800OTClient/data/images/game/minimap/flag10.png new file mode 100644 index 0000000..7cba49e Binary files /dev/null and b/800OTClient/data/images/game/minimap/flag10.png differ diff --git a/800OTClient/data/images/game/minimap/flag11.png b/800OTClient/data/images/game/minimap/flag11.png new file mode 100644 index 0000000..688175c Binary files /dev/null and b/800OTClient/data/images/game/minimap/flag11.png differ diff --git a/800OTClient/data/images/game/minimap/flag12.png b/800OTClient/data/images/game/minimap/flag12.png new file mode 100644 index 0000000..63ce6bf Binary files /dev/null and b/800OTClient/data/images/game/minimap/flag12.png differ diff --git a/800OTClient/data/images/game/minimap/flag13.png b/800OTClient/data/images/game/minimap/flag13.png new file mode 100644 index 0000000..1ffb377 Binary files /dev/null and b/800OTClient/data/images/game/minimap/flag13.png differ diff --git a/800OTClient/data/images/game/minimap/flag14.png b/800OTClient/data/images/game/minimap/flag14.png new file mode 100644 index 0000000..c9d5182 Binary files /dev/null and b/800OTClient/data/images/game/minimap/flag14.png differ diff --git a/800OTClient/data/images/game/minimap/flag15.png b/800OTClient/data/images/game/minimap/flag15.png new file mode 100644 index 0000000..fc9d692 Binary files /dev/null and b/800OTClient/data/images/game/minimap/flag15.png differ diff --git a/800OTClient/data/images/game/minimap/flag16.png b/800OTClient/data/images/game/minimap/flag16.png new file mode 100644 index 0000000..da226e1 Binary files /dev/null and b/800OTClient/data/images/game/minimap/flag16.png differ diff --git a/800OTClient/data/images/game/minimap/flag17.png b/800OTClient/data/images/game/minimap/flag17.png new file mode 100644 index 0000000..4000460 Binary files /dev/null and b/800OTClient/data/images/game/minimap/flag17.png differ diff --git a/800OTClient/data/images/game/minimap/flag18.png b/800OTClient/data/images/game/minimap/flag18.png new file mode 100644 index 0000000..7a625e7 Binary files /dev/null and b/800OTClient/data/images/game/minimap/flag18.png differ diff --git a/800OTClient/data/images/game/minimap/flag19.png b/800OTClient/data/images/game/minimap/flag19.png new file mode 100644 index 0000000..8f46613 Binary files /dev/null and b/800OTClient/data/images/game/minimap/flag19.png differ diff --git a/800OTClient/data/images/game/minimap/flag2.png b/800OTClient/data/images/game/minimap/flag2.png new file mode 100644 index 0000000..226eb55 Binary files /dev/null and b/800OTClient/data/images/game/minimap/flag2.png differ diff --git a/800OTClient/data/images/game/minimap/flag3.png b/800OTClient/data/images/game/minimap/flag3.png new file mode 100644 index 0000000..1d7f8ef Binary files /dev/null and b/800OTClient/data/images/game/minimap/flag3.png differ diff --git a/800OTClient/data/images/game/minimap/flag4.png b/800OTClient/data/images/game/minimap/flag4.png new file mode 100644 index 0000000..3f53f9d Binary files /dev/null and b/800OTClient/data/images/game/minimap/flag4.png differ diff --git a/800OTClient/data/images/game/minimap/flag5.png b/800OTClient/data/images/game/minimap/flag5.png new file mode 100644 index 0000000..8badc69 Binary files /dev/null and b/800OTClient/data/images/game/minimap/flag5.png differ diff --git a/800OTClient/data/images/game/minimap/flag6.png b/800OTClient/data/images/game/minimap/flag6.png new file mode 100644 index 0000000..8b71310 Binary files /dev/null and b/800OTClient/data/images/game/minimap/flag6.png differ diff --git a/800OTClient/data/images/game/minimap/flag7.png b/800OTClient/data/images/game/minimap/flag7.png new file mode 100644 index 0000000..866d079 Binary files /dev/null and b/800OTClient/data/images/game/minimap/flag7.png differ diff --git a/800OTClient/data/images/game/minimap/flag8.png b/800OTClient/data/images/game/minimap/flag8.png new file mode 100644 index 0000000..3c98633 Binary files /dev/null and b/800OTClient/data/images/game/minimap/flag8.png differ diff --git a/800OTClient/data/images/game/minimap/flag9.png b/800OTClient/data/images/game/minimap/flag9.png new file mode 100644 index 0000000..7a625e7 Binary files /dev/null and b/800OTClient/data/images/game/minimap/flag9.png differ diff --git a/800OTClient/data/images/game/minimap/flagcheckbox.png b/800OTClient/data/images/game/minimap/flagcheckbox.png new file mode 100644 index 0000000..b479c40 Binary files /dev/null and b/800OTClient/data/images/game/minimap/flagcheckbox.png differ diff --git a/800OTClient/data/images/game/minimap/floor_down.png b/800OTClient/data/images/game/minimap/floor_down.png new file mode 100644 index 0000000..b508610 Binary files /dev/null and b/800OTClient/data/images/game/minimap/floor_down.png differ diff --git a/800OTClient/data/images/game/minimap/floor_up.png b/800OTClient/data/images/game/minimap/floor_up.png new file mode 100644 index 0000000..804aa9e Binary files /dev/null and b/800OTClient/data/images/game/minimap/floor_up.png differ diff --git a/800OTClient/data/images/game/minimap/zoom_in.png b/800OTClient/data/images/game/minimap/zoom_in.png new file mode 100644 index 0000000..cca4c63 Binary files /dev/null and b/800OTClient/data/images/game/minimap/zoom_in.png differ diff --git a/800OTClient/data/images/game/minimap/zoom_out.png b/800OTClient/data/images/game/minimap/zoom_out.png new file mode 100644 index 0000000..6a56dd4 Binary files /dev/null and b/800OTClient/data/images/game/minimap/zoom_out.png differ diff --git a/800OTClient/data/images/game/mobile/attack.png b/800OTClient/data/images/game/mobile/attack.png new file mode 100644 index 0000000..bea4701 Binary files /dev/null and b/800OTClient/data/images/game/mobile/attack.png differ diff --git a/800OTClient/data/images/game/mobile/chat.png b/800OTClient/data/images/game/mobile/chat.png new file mode 100644 index 0000000..ae3002b Binary files /dev/null and b/800OTClient/data/images/game/mobile/chat.png differ diff --git a/800OTClient/data/images/game/mobile/follow.png b/800OTClient/data/images/game/mobile/follow.png new file mode 100644 index 0000000..760487c Binary files /dev/null and b/800OTClient/data/images/game/mobile/follow.png differ diff --git a/800OTClient/data/images/game/mobile/keypad.png b/800OTClient/data/images/game/mobile/keypad.png new file mode 100644 index 0000000..7198080 Binary files /dev/null and b/800OTClient/data/images/game/mobile/keypad.png differ diff --git a/800OTClient/data/images/game/mobile/keypad_pointer.png b/800OTClient/data/images/game/mobile/keypad_pointer.png new file mode 100644 index 0000000..c8385d3 Binary files /dev/null and b/800OTClient/data/images/game/mobile/keypad_pointer.png differ diff --git a/800OTClient/data/images/game/mobile/look.png b/800OTClient/data/images/game/mobile/look.png new file mode 100644 index 0000000..bbdf54e Binary files /dev/null and b/800OTClient/data/images/game/mobile/look.png differ diff --git a/800OTClient/data/images/game/mobile/use.png b/800OTClient/data/images/game/mobile/use.png new file mode 100644 index 0000000..596954d Binary files /dev/null and b/800OTClient/data/images/game/mobile/use.png differ diff --git a/800OTClient/data/images/game/npcicons/icon_chat.png b/800OTClient/data/images/game/npcicons/icon_chat.png new file mode 100644 index 0000000..b0da15e Binary files /dev/null and b/800OTClient/data/images/game/npcicons/icon_chat.png differ diff --git a/800OTClient/data/images/game/npcicons/icon_quest.png b/800OTClient/data/images/game/npcicons/icon_quest.png new file mode 100644 index 0000000..dcc8860 Binary files /dev/null and b/800OTClient/data/images/game/npcicons/icon_quest.png differ diff --git a/800OTClient/data/images/game/npcicons/icon_trade.png b/800OTClient/data/images/game/npcicons/icon_trade.png new file mode 100644 index 0000000..87db9f0 Binary files /dev/null and b/800OTClient/data/images/game/npcicons/icon_trade.png differ diff --git a/800OTClient/data/images/game/npcicons/icon_tradequest.png b/800OTClient/data/images/game/npcicons/icon_tradequest.png new file mode 100644 index 0000000..7f90a6c Binary files /dev/null and b/800OTClient/data/images/game/npcicons/icon_tradequest.png differ diff --git a/800OTClient/data/images/game/prey/prey_bigdamage.png b/800OTClient/data/images/game/prey/prey_bigdamage.png new file mode 100644 index 0000000..d719a85 Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_bigdamage.png differ diff --git a/800OTClient/data/images/game/prey/prey_bigdefense.png b/800OTClient/data/images/game/prey/prey_bigdefense.png new file mode 100644 index 0000000..0654bee Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_bigdefense.png differ diff --git a/800OTClient/data/images/game/prey/prey_biginactive.png b/800OTClient/data/images/game/prey/prey_biginactive.png new file mode 100644 index 0000000..5bfa760 Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_biginactive.png differ diff --git a/800OTClient/data/images/game/prey/prey_bigloot.png b/800OTClient/data/images/game/prey/prey_bigloot.png new file mode 100644 index 0000000..ed69241 Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_bigloot.png differ diff --git a/800OTClient/data/images/game/prey/prey_bignobonus.png b/800OTClient/data/images/game/prey/prey_bignobonus.png new file mode 100644 index 0000000..3332cf1 Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_bignobonus.png differ diff --git a/800OTClient/data/images/game/prey/prey_bigxp.png b/800OTClient/data/images/game/prey/prey_bigxp.png new file mode 100644 index 0000000..c3ce0b4 Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_bigxp.png differ diff --git a/800OTClient/data/images/game/prey/prey_bonus_reroll.png b/800OTClient/data/images/game/prey/prey_bonus_reroll.png new file mode 100644 index 0000000..beea588 Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_bonus_reroll.png differ diff --git a/800OTClient/data/images/game/prey/prey_choose.png b/800OTClient/data/images/game/prey/prey_choose.png new file mode 100644 index 0000000..734e0b3 Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_choose.png differ diff --git a/800OTClient/data/images/game/prey/prey_damage.png b/800OTClient/data/images/game/prey/prey_damage.png new file mode 100644 index 0000000..e36763b Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_damage.png differ diff --git a/800OTClient/data/images/game/prey/prey_defense.png b/800OTClient/data/images/game/prey/prey_defense.png new file mode 100644 index 0000000..824af0c Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_defense.png differ diff --git a/800OTClient/data/images/game/prey/prey_gold.png b/800OTClient/data/images/game/prey/prey_gold.png new file mode 100644 index 0000000..c9ed8ef Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_gold.png differ diff --git a/800OTClient/data/images/game/prey/prey_inactive.png b/800OTClient/data/images/game/prey/prey_inactive.png new file mode 100644 index 0000000..0df3385 Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_inactive.png differ diff --git a/800OTClient/data/images/game/prey/prey_loot.png b/800OTClient/data/images/game/prey/prey_loot.png new file mode 100644 index 0000000..8bda60c Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_loot.png differ diff --git a/800OTClient/data/images/game/prey/prey_no_bonus.png b/800OTClient/data/images/game/prey/prey_no_bonus.png new file mode 100644 index 0000000..db783ed Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_no_bonus.png differ diff --git a/800OTClient/data/images/game/prey/prey_nostar.png b/800OTClient/data/images/game/prey/prey_nostar.png new file mode 100644 index 0000000..a1892e9 Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_nostar.png differ diff --git a/800OTClient/data/images/game/prey/prey_perm.png b/800OTClient/data/images/game/prey/prey_perm.png new file mode 100644 index 0000000..90d9854 Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_perm.png differ diff --git a/800OTClient/data/images/game/prey/prey_perm_test.png b/800OTClient/data/images/game/prey/prey_perm_test.png new file mode 100644 index 0000000..ae88a43 Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_perm_test.png differ diff --git a/800OTClient/data/images/game/prey/prey_reroll.png b/800OTClient/data/images/game/prey/prey_reroll.png new file mode 100644 index 0000000..24e0051 Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_reroll.png differ diff --git a/800OTClient/data/images/game/prey/prey_reroll_blocked.png b/800OTClient/data/images/game/prey/prey_reroll_blocked.png new file mode 100644 index 0000000..ff4dc84 Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_reroll_blocked.png differ diff --git a/800OTClient/data/images/game/prey/prey_select.png b/800OTClient/data/images/game/prey/prey_select.png new file mode 100644 index 0000000..1a96e5b Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_select.png differ diff --git a/800OTClient/data/images/game/prey/prey_select_blocked.png b/800OTClient/data/images/game/prey/prey_select_blocked.png new file mode 100644 index 0000000..dbad971 Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_select_blocked.png differ diff --git a/800OTClient/data/images/game/prey/prey_smallstore.png b/800OTClient/data/images/game/prey/prey_smallstore.png new file mode 100644 index 0000000..d9b622a Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_smallstore.png differ diff --git a/800OTClient/data/images/game/prey/prey_star.png b/800OTClient/data/images/game/prey/prey_star.png new file mode 100644 index 0000000..58f04b1 Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_star.png differ diff --git a/800OTClient/data/images/game/prey/prey_temp.png b/800OTClient/data/images/game/prey/prey_temp.png new file mode 100644 index 0000000..86638c8 Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_temp.png differ diff --git a/800OTClient/data/images/game/prey/prey_temp_test.png b/800OTClient/data/images/game/prey/prey_temp_test.png new file mode 100644 index 0000000..9d87d07 Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_temp_test.png differ diff --git a/800OTClient/data/images/game/prey/prey_wildcard.png b/800OTClient/data/images/game/prey/prey_wildcard.png new file mode 100644 index 0000000..e4baf3f Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_wildcard.png differ diff --git a/800OTClient/data/images/game/prey/prey_xp.png b/800OTClient/data/images/game/prey/prey_xp.png new file mode 100644 index 0000000..da76fca Binary files /dev/null and b/800OTClient/data/images/game/prey/prey_xp.png differ diff --git a/800OTClient/data/images/game/shields/shield_blue.png b/800OTClient/data/images/game/shields/shield_blue.png new file mode 100644 index 0000000..2d05797 Binary files /dev/null and b/800OTClient/data/images/game/shields/shield_blue.png differ diff --git a/800OTClient/data/images/game/shields/shield_blue_not_shared.png b/800OTClient/data/images/game/shields/shield_blue_not_shared.png new file mode 100644 index 0000000..6bd6a78 Binary files /dev/null and b/800OTClient/data/images/game/shields/shield_blue_not_shared.png differ diff --git a/800OTClient/data/images/game/shields/shield_blue_shared.png b/800OTClient/data/images/game/shields/shield_blue_shared.png new file mode 100644 index 0000000..4cdc2b7 Binary files /dev/null and b/800OTClient/data/images/game/shields/shield_blue_shared.png differ diff --git a/800OTClient/data/images/game/shields/shield_blue_white.png b/800OTClient/data/images/game/shields/shield_blue_white.png new file mode 100644 index 0000000..f1aa8fe Binary files /dev/null and b/800OTClient/data/images/game/shields/shield_blue_white.png differ diff --git a/800OTClient/data/images/game/shields/shield_gray.png b/800OTClient/data/images/game/shields/shield_gray.png new file mode 100644 index 0000000..aa4689e Binary files /dev/null and b/800OTClient/data/images/game/shields/shield_gray.png differ diff --git a/800OTClient/data/images/game/shields/shield_yellow.png b/800OTClient/data/images/game/shields/shield_yellow.png new file mode 100644 index 0000000..eaee81c Binary files /dev/null and b/800OTClient/data/images/game/shields/shield_yellow.png differ diff --git a/800OTClient/data/images/game/shields/shield_yellow_not_shared.png b/800OTClient/data/images/game/shields/shield_yellow_not_shared.png new file mode 100644 index 0000000..85b0b30 Binary files /dev/null and b/800OTClient/data/images/game/shields/shield_yellow_not_shared.png differ diff --git a/800OTClient/data/images/game/shields/shield_yellow_shared.png b/800OTClient/data/images/game/shields/shield_yellow_shared.png new file mode 100644 index 0000000..196c4fd Binary files /dev/null and b/800OTClient/data/images/game/shields/shield_yellow_shared.png differ diff --git a/800OTClient/data/images/game/shields/shield_yellow_white.png b/800OTClient/data/images/game/shields/shield_yellow_white.png new file mode 100644 index 0000000..7dc9899 Binary files /dev/null and b/800OTClient/data/images/game/shields/shield_yellow_white.png differ diff --git a/800OTClient/data/images/game/skull_socket.png b/800OTClient/data/images/game/skull_socket.png new file mode 100644 index 0000000..a2d30e2 Binary files /dev/null and b/800OTClient/data/images/game/skull_socket.png differ diff --git a/800OTClient/data/images/game/skulls/skull_black.png b/800OTClient/data/images/game/skulls/skull_black.png new file mode 100644 index 0000000..8d3ddc0 Binary files /dev/null and b/800OTClient/data/images/game/skulls/skull_black.png differ diff --git a/800OTClient/data/images/game/skulls/skull_green.png b/800OTClient/data/images/game/skulls/skull_green.png new file mode 100644 index 0000000..382461f Binary files /dev/null and b/800OTClient/data/images/game/skulls/skull_green.png differ diff --git a/800OTClient/data/images/game/skulls/skull_orange.png b/800OTClient/data/images/game/skulls/skull_orange.png new file mode 100644 index 0000000..0c906c1 Binary files /dev/null and b/800OTClient/data/images/game/skulls/skull_orange.png differ diff --git a/800OTClient/data/images/game/skulls/skull_red.png b/800OTClient/data/images/game/skulls/skull_red.png new file mode 100644 index 0000000..67245fa Binary files /dev/null and b/800OTClient/data/images/game/skulls/skull_red.png differ diff --git a/800OTClient/data/images/game/skulls/skull_white.png b/800OTClient/data/images/game/skulls/skull_white.png new file mode 100644 index 0000000..e2c3d55 Binary files /dev/null and b/800OTClient/data/images/game/skulls/skull_white.png differ diff --git a/800OTClient/data/images/game/skulls/skull_yellow.png b/800OTClient/data/images/game/skulls/skull_yellow.png new file mode 100644 index 0000000..2994f8e Binary files /dev/null and b/800OTClient/data/images/game/skulls/skull_yellow.png differ diff --git a/800OTClient/data/images/game/slots/ammo-blessed.png b/800OTClient/data/images/game/slots/ammo-blessed.png new file mode 100644 index 0000000..bed31d7 Binary files /dev/null and b/800OTClient/data/images/game/slots/ammo-blessed.png differ diff --git a/800OTClient/data/images/game/slots/ammo.png b/800OTClient/data/images/game/slots/ammo.png new file mode 100644 index 0000000..345415f Binary files /dev/null and b/800OTClient/data/images/game/slots/ammo.png differ diff --git a/800OTClient/data/images/game/slots/back-blessed.png b/800OTClient/data/images/game/slots/back-blessed.png new file mode 100644 index 0000000..b369e6e Binary files /dev/null and b/800OTClient/data/images/game/slots/back-blessed.png differ diff --git a/800OTClient/data/images/game/slots/back.png b/800OTClient/data/images/game/slots/back.png new file mode 100644 index 0000000..dc874a7 Binary files /dev/null and b/800OTClient/data/images/game/slots/back.png differ diff --git a/800OTClient/data/images/game/slots/body-blessed.png b/800OTClient/data/images/game/slots/body-blessed.png new file mode 100644 index 0000000..caa01ba Binary files /dev/null and b/800OTClient/data/images/game/slots/body-blessed.png differ diff --git a/800OTClient/data/images/game/slots/body.png b/800OTClient/data/images/game/slots/body.png new file mode 100644 index 0000000..78dcbe2 Binary files /dev/null and b/800OTClient/data/images/game/slots/body.png differ diff --git a/800OTClient/data/images/game/slots/coins.png b/800OTClient/data/images/game/slots/coins.png new file mode 100644 index 0000000..c8fe570 Binary files /dev/null and b/800OTClient/data/images/game/slots/coins.png differ diff --git a/800OTClient/data/images/game/slots/feet-blessed.png b/800OTClient/data/images/game/slots/feet-blessed.png new file mode 100644 index 0000000..29c011f Binary files /dev/null and b/800OTClient/data/images/game/slots/feet-blessed.png differ diff --git a/800OTClient/data/images/game/slots/feet.png b/800OTClient/data/images/game/slots/feet.png new file mode 100644 index 0000000..4bdfd5f Binary files /dev/null and b/800OTClient/data/images/game/slots/feet.png differ diff --git a/800OTClient/data/images/game/slots/finger-blessed.png b/800OTClient/data/images/game/slots/finger-blessed.png new file mode 100644 index 0000000..575f34d Binary files /dev/null and b/800OTClient/data/images/game/slots/finger-blessed.png differ diff --git a/800OTClient/data/images/game/slots/finger.png b/800OTClient/data/images/game/slots/finger.png new file mode 100644 index 0000000..61dec1e Binary files /dev/null and b/800OTClient/data/images/game/slots/finger.png differ diff --git a/800OTClient/data/images/game/slots/head-blessed.png b/800OTClient/data/images/game/slots/head-blessed.png new file mode 100644 index 0000000..418f4f6 Binary files /dev/null and b/800OTClient/data/images/game/slots/head-blessed.png differ diff --git a/800OTClient/data/images/game/slots/head.png b/800OTClient/data/images/game/slots/head.png new file mode 100644 index 0000000..f2f3782 Binary files /dev/null and b/800OTClient/data/images/game/slots/head.png differ diff --git a/800OTClient/data/images/game/slots/left-hand-blessed.png b/800OTClient/data/images/game/slots/left-hand-blessed.png new file mode 100644 index 0000000..6140de8 Binary files /dev/null and b/800OTClient/data/images/game/slots/left-hand-blessed.png differ diff --git a/800OTClient/data/images/game/slots/left-hand.png b/800OTClient/data/images/game/slots/left-hand.png new file mode 100644 index 0000000..7ca84cf Binary files /dev/null and b/800OTClient/data/images/game/slots/left-hand.png differ diff --git a/800OTClient/data/images/game/slots/legs-blessed.png b/800OTClient/data/images/game/slots/legs-blessed.png new file mode 100644 index 0000000..759f13b Binary files /dev/null and b/800OTClient/data/images/game/slots/legs-blessed.png differ diff --git a/800OTClient/data/images/game/slots/legs.png b/800OTClient/data/images/game/slots/legs.png new file mode 100644 index 0000000..145121a Binary files /dev/null and b/800OTClient/data/images/game/slots/legs.png differ diff --git a/800OTClient/data/images/game/slots/neck-blessed.png b/800OTClient/data/images/game/slots/neck-blessed.png new file mode 100644 index 0000000..e3b6875 Binary files /dev/null and b/800OTClient/data/images/game/slots/neck-blessed.png differ diff --git a/800OTClient/data/images/game/slots/neck.png b/800OTClient/data/images/game/slots/neck.png new file mode 100644 index 0000000..94a4e55 Binary files /dev/null and b/800OTClient/data/images/game/slots/neck.png differ diff --git a/800OTClient/data/images/game/slots/purse.png b/800OTClient/data/images/game/slots/purse.png new file mode 100644 index 0000000..b2667a1 Binary files /dev/null and b/800OTClient/data/images/game/slots/purse.png differ diff --git a/800OTClient/data/images/game/slots/right-hand-blessed.png b/800OTClient/data/images/game/slots/right-hand-blessed.png new file mode 100644 index 0000000..4a6bd9b Binary files /dev/null and b/800OTClient/data/images/game/slots/right-hand-blessed.png differ diff --git a/800OTClient/data/images/game/slots/right-hand.png b/800OTClient/data/images/game/slots/right-hand.png new file mode 100644 index 0000000..0aa355b Binary files /dev/null and b/800OTClient/data/images/game/slots/right-hand.png differ diff --git a/800OTClient/data/images/game/slots/soulcap.png b/800OTClient/data/images/game/slots/soulcap.png new file mode 100644 index 0000000..174d0df Binary files /dev/null and b/800OTClient/data/images/game/slots/soulcap.png differ diff --git a/800OTClient/data/images/game/spells/cooldowns.png b/800OTClient/data/images/game/spells/cooldowns.png new file mode 100644 index 0000000..aad66de Binary files /dev/null and b/800OTClient/data/images/game/spells/cooldowns.png differ diff --git a/800OTClient/data/images/game/spells/defaultspells.png b/800OTClient/data/images/game/spells/defaultspells.png new file mode 100644 index 0000000..0ec0edc Binary files /dev/null and b/800OTClient/data/images/game/spells/defaultspells.png differ diff --git a/800OTClient/data/images/game/states/bleeding.png b/800OTClient/data/images/game/states/bleeding.png new file mode 100644 index 0000000..024ee7e Binary files /dev/null and b/800OTClient/data/images/game/states/bleeding.png differ diff --git a/800OTClient/data/images/game/states/burning.png b/800OTClient/data/images/game/states/burning.png new file mode 100644 index 0000000..9d503ca Binary files /dev/null and b/800OTClient/data/images/game/states/burning.png differ diff --git a/800OTClient/data/images/game/states/cursed.png b/800OTClient/data/images/game/states/cursed.png new file mode 100644 index 0000000..6171bd9 Binary files /dev/null and b/800OTClient/data/images/game/states/cursed.png differ diff --git a/800OTClient/data/images/game/states/dazzled.png b/800OTClient/data/images/game/states/dazzled.png new file mode 100644 index 0000000..01e42ac Binary files /dev/null and b/800OTClient/data/images/game/states/dazzled.png differ diff --git a/800OTClient/data/images/game/states/drowning.png b/800OTClient/data/images/game/states/drowning.png new file mode 100644 index 0000000..88c4dad Binary files /dev/null and b/800OTClient/data/images/game/states/drowning.png differ diff --git a/800OTClient/data/images/game/states/drunk.png b/800OTClient/data/images/game/states/drunk.png new file mode 100644 index 0000000..e83af44 Binary files /dev/null and b/800OTClient/data/images/game/states/drunk.png differ diff --git a/800OTClient/data/images/game/states/electrified.png b/800OTClient/data/images/game/states/electrified.png new file mode 100644 index 0000000..38e67a8 Binary files /dev/null and b/800OTClient/data/images/game/states/electrified.png differ diff --git a/800OTClient/data/images/game/states/freezing.png b/800OTClient/data/images/game/states/freezing.png new file mode 100644 index 0000000..04acfb0 Binary files /dev/null and b/800OTClient/data/images/game/states/freezing.png differ diff --git a/800OTClient/data/images/game/states/haste.png b/800OTClient/data/images/game/states/haste.png new file mode 100644 index 0000000..9f39a96 Binary files /dev/null and b/800OTClient/data/images/game/states/haste.png differ diff --git a/800OTClient/data/images/game/states/hungry.png b/800OTClient/data/images/game/states/hungry.png new file mode 100644 index 0000000..829e191 Binary files /dev/null and b/800OTClient/data/images/game/states/hungry.png differ diff --git a/800OTClient/data/images/game/states/logout_block.png b/800OTClient/data/images/game/states/logout_block.png new file mode 100644 index 0000000..4244dfe Binary files /dev/null and b/800OTClient/data/images/game/states/logout_block.png differ diff --git a/800OTClient/data/images/game/states/magic_shield.png b/800OTClient/data/images/game/states/magic_shield.png new file mode 100644 index 0000000..4286a01 Binary files /dev/null and b/800OTClient/data/images/game/states/magic_shield.png differ diff --git a/800OTClient/data/images/game/states/poisoned.png b/800OTClient/data/images/game/states/poisoned.png new file mode 100644 index 0000000..3aae9cc Binary files /dev/null and b/800OTClient/data/images/game/states/poisoned.png differ diff --git a/800OTClient/data/images/game/states/protection_zone.png b/800OTClient/data/images/game/states/protection_zone.png new file mode 100644 index 0000000..741f4df Binary files /dev/null and b/800OTClient/data/images/game/states/protection_zone.png differ diff --git a/800OTClient/data/images/game/states/protection_zone_block.png b/800OTClient/data/images/game/states/protection_zone_block.png new file mode 100644 index 0000000..47bcade Binary files /dev/null and b/800OTClient/data/images/game/states/protection_zone_block.png differ diff --git a/800OTClient/data/images/game/states/slowed.png b/800OTClient/data/images/game/states/slowed.png new file mode 100644 index 0000000..b1ab240 Binary files /dev/null and b/800OTClient/data/images/game/states/slowed.png differ diff --git a/800OTClient/data/images/game/states/strengthened.png b/800OTClient/data/images/game/states/strengthened.png new file mode 100644 index 0000000..29e827d Binary files /dev/null and b/800OTClient/data/images/game/states/strengthened.png differ diff --git a/800OTClient/data/images/game/topbar/boost.png b/800OTClient/data/images/game/topbar/boost.png new file mode 100644 index 0000000..f7bbc64 Binary files /dev/null and b/800OTClient/data/images/game/topbar/boost.png differ diff --git a/800OTClient/data/images/game/topbar/icons.png b/800OTClient/data/images/game/topbar/icons.png new file mode 100644 index 0000000..cfa2352 Binary files /dev/null and b/800OTClient/data/images/game/topbar/icons.png differ diff --git a/800OTClient/data/images/game/viplist/icons.png b/800OTClient/data/images/game/viplist/icons.png new file mode 100644 index 0000000..e0d67cc Binary files /dev/null and b/800OTClient/data/images/game/viplist/icons.png differ diff --git a/800OTClient/data/images/game/viplist/vipcheckbox.png b/800OTClient/data/images/game/viplist/vipcheckbox.png new file mode 100644 index 0000000..b479c40 Binary files /dev/null and b/800OTClient/data/images/game/viplist/vipcheckbox.png differ diff --git a/800OTClient/data/images/loading.png b/800OTClient/data/images/loading.png new file mode 100644 index 0000000..de8dff6 Binary files /dev/null and b/800OTClient/data/images/loading.png differ diff --git a/800OTClient/data/images/optionstab/audio.png b/800OTClient/data/images/optionstab/audio.png new file mode 100644 index 0000000..e3aee23 Binary files /dev/null and b/800OTClient/data/images/optionstab/audio.png differ diff --git a/800OTClient/data/images/optionstab/console.png b/800OTClient/data/images/optionstab/console.png new file mode 100644 index 0000000..d9ce1db Binary files /dev/null and b/800OTClient/data/images/optionstab/console.png differ diff --git a/800OTClient/data/images/optionstab/extras.png b/800OTClient/data/images/optionstab/extras.png new file mode 100644 index 0000000..ace46d8 Binary files /dev/null and b/800OTClient/data/images/optionstab/extras.png differ diff --git a/800OTClient/data/images/optionstab/features.png b/800OTClient/data/images/optionstab/features.png new file mode 100644 index 0000000..4cf47fc Binary files /dev/null and b/800OTClient/data/images/optionstab/features.png differ diff --git a/800OTClient/data/images/optionstab/game.png b/800OTClient/data/images/optionstab/game.png new file mode 100644 index 0000000..40892e4 Binary files /dev/null and b/800OTClient/data/images/optionstab/game.png differ diff --git a/800OTClient/data/images/optionstab/graphics.png b/800OTClient/data/images/optionstab/graphics.png new file mode 100644 index 0000000..ace46d8 Binary files /dev/null and b/800OTClient/data/images/optionstab/graphics.png differ diff --git a/800OTClient/data/images/shaders/brazil.png b/800OTClient/data/images/shaders/brazil.png new file mode 100644 index 0000000..0c75047 Binary files /dev/null and b/800OTClient/data/images/shaders/brazil.png differ diff --git a/800OTClient/data/images/shaders/gold.png b/800OTClient/data/images/shaders/gold.png new file mode 100644 index 0000000..5bb7700 Binary files /dev/null and b/800OTClient/data/images/shaders/gold.png differ diff --git a/800OTClient/data/images/shaders/rainbow.png b/800OTClient/data/images/shaders/rainbow.png new file mode 100644 index 0000000..2ee8654 Binary files /dev/null and b/800OTClient/data/images/shaders/rainbow.png differ diff --git a/800OTClient/data/images/shaders/stars.png b/800OTClient/data/images/shaders/stars.png new file mode 100644 index 0000000..722e390 Binary files /dev/null and b/800OTClient/data/images/shaders/stars.png differ diff --git a/800OTClient/data/images/shaders/sweden.png b/800OTClient/data/images/shaders/sweden.png new file mode 100644 index 0000000..e7a52d4 Binary files /dev/null and b/800OTClient/data/images/shaders/sweden.png differ diff --git a/800OTClient/data/images/topbuttons/analyzers.png b/800OTClient/data/images/topbuttons/analyzers.png new file mode 100644 index 0000000..de8294d Binary files /dev/null and b/800OTClient/data/images/topbuttons/analyzers.png differ diff --git a/800OTClient/data/images/topbuttons/audio.png b/800OTClient/data/images/topbuttons/audio.png new file mode 100644 index 0000000..7bac9e9 Binary files /dev/null and b/800OTClient/data/images/topbuttons/audio.png differ diff --git a/800OTClient/data/images/topbuttons/audio_mute.png b/800OTClient/data/images/topbuttons/audio_mute.png new file mode 100644 index 0000000..a7dda9a Binary files /dev/null and b/800OTClient/data/images/topbuttons/audio_mute.png differ diff --git a/800OTClient/data/images/topbuttons/battle.png b/800OTClient/data/images/topbuttons/battle.png new file mode 100644 index 0000000..ed67347 Binary files /dev/null and b/800OTClient/data/images/topbuttons/battle.png differ diff --git a/800OTClient/data/images/topbuttons/bot.png b/800OTClient/data/images/topbuttons/bot.png new file mode 100644 index 0000000..8dc7d11 Binary files /dev/null and b/800OTClient/data/images/topbuttons/bot.png differ diff --git a/800OTClient/data/images/topbuttons/buttons.png b/800OTClient/data/images/topbuttons/buttons.png new file mode 100644 index 0000000..01cb280 Binary files /dev/null and b/800OTClient/data/images/topbuttons/buttons.png differ diff --git a/800OTClient/data/images/topbuttons/combatcontrols.png b/800OTClient/data/images/topbuttons/combatcontrols.png new file mode 100644 index 0000000..f39523e Binary files /dev/null and b/800OTClient/data/images/topbuttons/combatcontrols.png differ diff --git a/800OTClient/data/images/topbuttons/cooldowns.png b/800OTClient/data/images/topbuttons/cooldowns.png new file mode 100644 index 0000000..0e66ccf Binary files /dev/null and b/800OTClient/data/images/topbuttons/cooldowns.png differ diff --git a/800OTClient/data/images/topbuttons/debug.png b/800OTClient/data/images/topbuttons/debug.png new file mode 100644 index 0000000..9768e4c Binary files /dev/null and b/800OTClient/data/images/topbuttons/debug.png differ diff --git a/800OTClient/data/images/topbuttons/healthinfo.png b/800OTClient/data/images/topbuttons/healthinfo.png new file mode 100644 index 0000000..c398bf6 Binary files /dev/null and b/800OTClient/data/images/topbuttons/healthinfo.png differ diff --git a/800OTClient/data/images/topbuttons/hotkeys.png b/800OTClient/data/images/topbuttons/hotkeys.png new file mode 100644 index 0000000..9f0c25e Binary files /dev/null and b/800OTClient/data/images/topbuttons/hotkeys.png differ diff --git a/800OTClient/data/images/topbuttons/inventory.png b/800OTClient/data/images/topbuttons/inventory.png new file mode 100644 index 0000000..27876d9 Binary files /dev/null and b/800OTClient/data/images/topbuttons/inventory.png differ diff --git a/800OTClient/data/images/topbuttons/keypad.png b/800OTClient/data/images/topbuttons/keypad.png new file mode 100644 index 0000000..ea01039 Binary files /dev/null and b/800OTClient/data/images/topbuttons/keypad.png differ diff --git a/800OTClient/data/images/topbuttons/login.png b/800OTClient/data/images/topbuttons/login.png new file mode 100644 index 0000000..55ec697 Binary files /dev/null and b/800OTClient/data/images/topbuttons/login.png differ diff --git a/800OTClient/data/images/topbuttons/logout.png b/800OTClient/data/images/topbuttons/logout.png new file mode 100644 index 0000000..91e2354 Binary files /dev/null and b/800OTClient/data/images/topbuttons/logout.png differ diff --git a/800OTClient/data/images/topbuttons/minimap.png b/800OTClient/data/images/topbuttons/minimap.png new file mode 100644 index 0000000..8ec6efe Binary files /dev/null and b/800OTClient/data/images/topbuttons/minimap.png differ diff --git a/800OTClient/data/images/topbuttons/modulemanager.png b/800OTClient/data/images/topbuttons/modulemanager.png new file mode 100644 index 0000000..8089991 Binary files /dev/null and b/800OTClient/data/images/topbuttons/modulemanager.png differ diff --git a/800OTClient/data/images/topbuttons/motd.png b/800OTClient/data/images/topbuttons/motd.png new file mode 100644 index 0000000..01cb280 Binary files /dev/null and b/800OTClient/data/images/topbuttons/motd.png differ diff --git a/800OTClient/data/images/topbuttons/options.png b/800OTClient/data/images/topbuttons/options.png new file mode 100644 index 0000000..3a8aaf7 Binary files /dev/null and b/800OTClient/data/images/topbuttons/options.png differ diff --git a/800OTClient/data/images/topbuttons/particles.png b/800OTClient/data/images/topbuttons/particles.png new file mode 100644 index 0000000..a52cc24 Binary files /dev/null and b/800OTClient/data/images/topbuttons/particles.png differ diff --git a/800OTClient/data/images/topbuttons/prey.png b/800OTClient/data/images/topbuttons/prey.png new file mode 100644 index 0000000..589412e Binary files /dev/null and b/800OTClient/data/images/topbuttons/prey.png differ diff --git a/800OTClient/data/images/topbuttons/prey_window.png b/800OTClient/data/images/topbuttons/prey_window.png new file mode 100644 index 0000000..dbbbc1e Binary files /dev/null and b/800OTClient/data/images/topbuttons/prey_window.png differ diff --git a/800OTClient/data/images/topbuttons/quest_tracker.png b/800OTClient/data/images/topbuttons/quest_tracker.png new file mode 100644 index 0000000..cf6948c Binary files /dev/null and b/800OTClient/data/images/topbuttons/quest_tracker.png differ diff --git a/800OTClient/data/images/topbuttons/questlog.png b/800OTClient/data/images/topbuttons/questlog.png new file mode 100644 index 0000000..0ad6ea4 Binary files /dev/null and b/800OTClient/data/images/topbuttons/questlog.png differ diff --git a/800OTClient/data/images/topbuttons/shop.png b/800OTClient/data/images/topbuttons/shop.png new file mode 100644 index 0000000..21d6980 Binary files /dev/null and b/800OTClient/data/images/topbuttons/shop.png differ diff --git a/800OTClient/data/images/topbuttons/skills.png b/800OTClient/data/images/topbuttons/skills.png new file mode 100644 index 0000000..52deb10 Binary files /dev/null and b/800OTClient/data/images/topbuttons/skills.png differ diff --git a/800OTClient/data/images/topbuttons/spelllist.png b/800OTClient/data/images/topbuttons/spelllist.png new file mode 100644 index 0000000..e1c01cc Binary files /dev/null and b/800OTClient/data/images/topbuttons/spelllist.png differ diff --git a/800OTClient/data/images/topbuttons/terminal.png b/800OTClient/data/images/topbuttons/terminal.png new file mode 100644 index 0000000..768c2e9 Binary files /dev/null and b/800OTClient/data/images/topbuttons/terminal.png differ diff --git a/800OTClient/data/images/topbuttons/unjustifiedpoints.png b/800OTClient/data/images/topbuttons/unjustifiedpoints.png new file mode 100644 index 0000000..67245fa Binary files /dev/null and b/800OTClient/data/images/topbuttons/unjustifiedpoints.png differ diff --git a/800OTClient/data/images/topbuttons/viplist.png b/800OTClient/data/images/topbuttons/viplist.png new file mode 100644 index 0000000..d0e69e7 Binary files /dev/null and b/800OTClient/data/images/topbuttons/viplist.png differ diff --git a/800OTClient/data/images/topbuttons/zoomin.png b/800OTClient/data/images/topbuttons/zoomin.png new file mode 100644 index 0000000..bce83ed Binary files /dev/null and b/800OTClient/data/images/topbuttons/zoomin.png differ diff --git a/800OTClient/data/images/topbuttons/zoomout.png b/800OTClient/data/images/topbuttons/zoomout.png new file mode 100644 index 0000000..1dd6f96 Binary files /dev/null and b/800OTClient/data/images/topbuttons/zoomout.png differ diff --git a/800OTClient/data/images/ui/android.png b/800OTClient/data/images/ui/android.png new file mode 100644 index 0000000..6d14f24 Binary files /dev/null and b/800OTClient/data/images/ui/android.png differ diff --git a/800OTClient/data/images/ui/arrow_horizontal.png b/800OTClient/data/images/ui/arrow_horizontal.png new file mode 100644 index 0000000..a0ec72c Binary files /dev/null and b/800OTClient/data/images/ui/arrow_horizontal.png differ diff --git a/800OTClient/data/images/ui/arrow_vertical.png b/800OTClient/data/images/ui/arrow_vertical.png new file mode 100644 index 0000000..d48aba3 Binary files /dev/null and b/800OTClient/data/images/ui/arrow_vertical.png differ diff --git a/800OTClient/data/images/ui/button.png b/800OTClient/data/images/ui/button.png new file mode 100644 index 0000000..103f617 Binary files /dev/null and b/800OTClient/data/images/ui/button.png differ diff --git a/800OTClient/data/images/ui/button_popupmenu.png b/800OTClient/data/images/ui/button_popupmenu.png new file mode 100644 index 0000000..103f617 Binary files /dev/null and b/800OTClient/data/images/ui/button_popupmenu.png differ diff --git a/800OTClient/data/images/ui/button_rounded.png b/800OTClient/data/images/ui/button_rounded.png new file mode 100644 index 0000000..eebfa2f Binary files /dev/null and b/800OTClient/data/images/ui/button_rounded.png differ diff --git a/800OTClient/data/images/ui/button_square.png b/800OTClient/data/images/ui/button_square.png new file mode 100644 index 0000000..103f617 Binary files /dev/null and b/800OTClient/data/images/ui/button_square.png differ diff --git a/800OTClient/data/images/ui/button_top.png b/800OTClient/data/images/ui/button_top.png new file mode 100644 index 0000000..73703a0 Binary files /dev/null and b/800OTClient/data/images/ui/button_top.png differ diff --git a/800OTClient/data/images/ui/button_top_blink.png b/800OTClient/data/images/ui/button_top_blink.png new file mode 100644 index 0000000..9a66927 Binary files /dev/null and b/800OTClient/data/images/ui/button_top_blink.png differ diff --git a/800OTClient/data/images/ui/button_topgame.png b/800OTClient/data/images/ui/button_topgame.png new file mode 100644 index 0000000..c6f115f Binary files /dev/null and b/800OTClient/data/images/ui/button_topgame.png differ diff --git a/800OTClient/data/images/ui/checkbox.png b/800OTClient/data/images/ui/checkbox.png new file mode 100644 index 0000000..e3aa5e1 Binary files /dev/null and b/800OTClient/data/images/ui/checkbox.png differ diff --git a/800OTClient/data/images/ui/checkbox_round.png b/800OTClient/data/images/ui/checkbox_round.png new file mode 100644 index 0000000..71672a7 Binary files /dev/null and b/800OTClient/data/images/ui/checkbox_round.png differ diff --git a/800OTClient/data/images/ui/colorbox.png b/800OTClient/data/images/ui/colorbox.png new file mode 100644 index 0000000..9d2ef7f Binary files /dev/null and b/800OTClient/data/images/ui/colorbox.png differ diff --git a/800OTClient/data/images/ui/combobox.png b/800OTClient/data/images/ui/combobox.png new file mode 100644 index 0000000..8ef29ab Binary files /dev/null and b/800OTClient/data/images/ui/combobox.png differ diff --git a/800OTClient/data/images/ui/combobox_rounded.png b/800OTClient/data/images/ui/combobox_rounded.png new file mode 100644 index 0000000..3c00702 Binary files /dev/null and b/800OTClient/data/images/ui/combobox_rounded.png differ diff --git a/800OTClient/data/images/ui/combobox_square.png b/800OTClient/data/images/ui/combobox_square.png new file mode 100644 index 0000000..8ef29ab Binary files /dev/null and b/800OTClient/data/images/ui/combobox_square.png differ diff --git a/800OTClient/data/images/ui/continue_with_logpass.png b/800OTClient/data/images/ui/continue_with_logpass.png new file mode 100644 index 0000000..7c611bb Binary files /dev/null and b/800OTClient/data/images/ui/continue_with_logpass.png differ diff --git a/800OTClient/data/images/ui/dark_background.png b/800OTClient/data/images/ui/dark_background.png new file mode 100644 index 0000000..9b8e992 Binary files /dev/null and b/800OTClient/data/images/ui/dark_background.png differ diff --git a/800OTClient/data/images/ui/discord.png b/800OTClient/data/images/ui/discord.png new file mode 100644 index 0000000..2e0d957 Binary files /dev/null and b/800OTClient/data/images/ui/discord.png differ diff --git a/800OTClient/data/images/ui/graph_background.png b/800OTClient/data/images/ui/graph_background.png new file mode 100644 index 0000000..71f0c12 Binary files /dev/null and b/800OTClient/data/images/ui/graph_background.png differ diff --git a/800OTClient/data/images/ui/icon_add.png b/800OTClient/data/images/ui/icon_add.png new file mode 100644 index 0000000..f820a0b Binary files /dev/null and b/800OTClient/data/images/ui/icon_add.png differ diff --git a/800OTClient/data/images/ui/ios.png b/800OTClient/data/images/ui/ios.png new file mode 100644 index 0000000..6c0ecb4 Binary files /dev/null and b/800OTClient/data/images/ui/ios.png differ diff --git a/800OTClient/data/images/ui/item-blessed.png b/800OTClient/data/images/ui/item-blessed.png new file mode 100644 index 0000000..41db9d9 Binary files /dev/null and b/800OTClient/data/images/ui/item-blessed.png differ diff --git a/800OTClient/data/images/ui/item.png b/800OTClient/data/images/ui/item.png new file mode 100644 index 0000000..4230ace Binary files /dev/null and b/800OTClient/data/images/ui/item.png differ diff --git a/800OTClient/data/images/ui/menubox.png b/800OTClient/data/images/ui/menubox.png new file mode 100644 index 0000000..307e5fd Binary files /dev/null and b/800OTClient/data/images/ui/menubox.png differ diff --git a/800OTClient/data/images/ui/miniwindow.png b/800OTClient/data/images/ui/miniwindow.png new file mode 100644 index 0000000..a8647c8 Binary files /dev/null and b/800OTClient/data/images/ui/miniwindow.png differ diff --git a/800OTClient/data/images/ui/miniwindow_buttons.png b/800OTClient/data/images/ui/miniwindow_buttons.png new file mode 100644 index 0000000..8bf5271 Binary files /dev/null and b/800OTClient/data/images/ui/miniwindow_buttons.png differ diff --git a/800OTClient/data/images/ui/otcicon.rc b/800OTClient/data/images/ui/otcicon.rc new file mode 100644 index 0000000..52a9a6e --- /dev/null +++ b/800OTClient/data/images/ui/otcicon.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "otcicon.ico" \ No newline at end of file diff --git a/800OTClient/data/images/ui/panel_bottom.png b/800OTClient/data/images/ui/panel_bottom.png new file mode 100644 index 0000000..8b9da27 Binary files /dev/null and b/800OTClient/data/images/ui/panel_bottom.png differ diff --git a/800OTClient/data/images/ui/panel_bottom2.png b/800OTClient/data/images/ui/panel_bottom2.png new file mode 100644 index 0000000..b01e73c Binary files /dev/null and b/800OTClient/data/images/ui/panel_bottom2.png differ diff --git a/800OTClient/data/images/ui/panel_container.png b/800OTClient/data/images/ui/panel_container.png new file mode 100644 index 0000000..23102a3 Binary files /dev/null and b/800OTClient/data/images/ui/panel_container.png differ diff --git a/800OTClient/data/images/ui/panel_content.png b/800OTClient/data/images/ui/panel_content.png new file mode 100644 index 0000000..209b28f Binary files /dev/null and b/800OTClient/data/images/ui/panel_content.png differ diff --git a/800OTClient/data/images/ui/panel_flat.png b/800OTClient/data/images/ui/panel_flat.png new file mode 100644 index 0000000..d32beb7 Binary files /dev/null and b/800OTClient/data/images/ui/panel_flat.png differ diff --git a/800OTClient/data/images/ui/panel_lightflat.png b/800OTClient/data/images/ui/panel_lightflat.png new file mode 100644 index 0000000..d32beb7 Binary files /dev/null and b/800OTClient/data/images/ui/panel_lightflat.png differ diff --git a/800OTClient/data/images/ui/panel_map.png b/800OTClient/data/images/ui/panel_map.png new file mode 100644 index 0000000..8b9da27 Binary files /dev/null and b/800OTClient/data/images/ui/panel_map.png differ diff --git a/800OTClient/data/images/ui/panel_side.png b/800OTClient/data/images/ui/panel_side.png new file mode 100644 index 0000000..8b9da27 Binary files /dev/null and b/800OTClient/data/images/ui/panel_side.png differ diff --git a/800OTClient/data/images/ui/panel_top.png b/800OTClient/data/images/ui/panel_top.png new file mode 100644 index 0000000..649874a Binary files /dev/null and b/800OTClient/data/images/ui/panel_top.png differ diff --git a/800OTClient/data/images/ui/progressbar.png b/800OTClient/data/images/ui/progressbar.png new file mode 100644 index 0000000..d139932 Binary files /dev/null and b/800OTClient/data/images/ui/progressbar.png differ diff --git a/800OTClient/data/images/ui/qauth.png b/800OTClient/data/images/ui/qauth.png new file mode 100644 index 0000000..12a45d3 Binary files /dev/null and b/800OTClient/data/images/ui/qauth.png differ diff --git a/800OTClient/data/images/ui/rarity_blue.png b/800OTClient/data/images/ui/rarity_blue.png new file mode 100644 index 0000000..13220bd Binary files /dev/null and b/800OTClient/data/images/ui/rarity_blue.png differ diff --git a/800OTClient/data/images/ui/rarity_frames.png b/800OTClient/data/images/ui/rarity_frames.png new file mode 100644 index 0000000..cebc6d9 Binary files /dev/null and b/800OTClient/data/images/ui/rarity_frames.png differ diff --git a/800OTClient/data/images/ui/rarity_gold.png b/800OTClient/data/images/ui/rarity_gold.png new file mode 100644 index 0000000..e38503e Binary files /dev/null and b/800OTClient/data/images/ui/rarity_gold.png differ diff --git a/800OTClient/data/images/ui/rarity_green.png b/800OTClient/data/images/ui/rarity_green.png new file mode 100644 index 0000000..b452b0f Binary files /dev/null and b/800OTClient/data/images/ui/rarity_green.png differ diff --git a/800OTClient/data/images/ui/rarity_purple.png b/800OTClient/data/images/ui/rarity_purple.png new file mode 100644 index 0000000..05faf5b Binary files /dev/null and b/800OTClient/data/images/ui/rarity_purple.png differ diff --git a/800OTClient/data/images/ui/rarity_white.png b/800OTClient/data/images/ui/rarity_white.png new file mode 100644 index 0000000..9984f9c Binary files /dev/null and b/800OTClient/data/images/ui/rarity_white.png differ diff --git a/800OTClient/data/images/ui/scrollbar.png b/800OTClient/data/images/ui/scrollbar.png new file mode 100644 index 0000000..f565ecd Binary files /dev/null and b/800OTClient/data/images/ui/scrollbar.png differ diff --git a/800OTClient/data/images/ui/separator_horizontal.png b/800OTClient/data/images/ui/separator_horizontal.png new file mode 100644 index 0000000..40484e3 Binary files /dev/null and b/800OTClient/data/images/ui/separator_horizontal.png differ diff --git a/800OTClient/data/images/ui/separator_vertical.png b/800OTClient/data/images/ui/separator_vertical.png new file mode 100644 index 0000000..ca3b5d3 Binary files /dev/null and b/800OTClient/data/images/ui/separator_vertical.png differ diff --git a/800OTClient/data/images/ui/spinbox.png b/800OTClient/data/images/ui/spinbox.png new file mode 100644 index 0000000..8029090 Binary files /dev/null and b/800OTClient/data/images/ui/spinbox.png differ diff --git a/800OTClient/data/images/ui/spinbox_down.png b/800OTClient/data/images/ui/spinbox_down.png new file mode 100644 index 0000000..0f99b8e Binary files /dev/null and b/800OTClient/data/images/ui/spinbox_down.png differ diff --git a/800OTClient/data/images/ui/spinbox_up.png b/800OTClient/data/images/ui/spinbox_up.png new file mode 100644 index 0000000..8432b2d Binary files /dev/null and b/800OTClient/data/images/ui/spinbox_up.png differ diff --git a/800OTClient/data/images/ui/tabbutton_rounded.png b/800OTClient/data/images/ui/tabbutton_rounded.png new file mode 100644 index 0000000..eebfa2f Binary files /dev/null and b/800OTClient/data/images/ui/tabbutton_rounded.png differ diff --git a/800OTClient/data/images/ui/tabbutton_square.png b/800OTClient/data/images/ui/tabbutton_square.png new file mode 100644 index 0000000..1e1c583 Binary files /dev/null and b/800OTClient/data/images/ui/tabbutton_square.png differ diff --git a/800OTClient/data/images/ui/textedit.png b/800OTClient/data/images/ui/textedit.png new file mode 100644 index 0000000..96062ef Binary files /dev/null and b/800OTClient/data/images/ui/textedit.png differ diff --git a/800OTClient/data/images/ui/window.png b/800OTClient/data/images/ui/window.png new file mode 100644 index 0000000..84b3740 Binary files /dev/null and b/800OTClient/data/images/ui/window.png differ diff --git a/800OTClient/data/images/ui/window_headless.png b/800OTClient/data/images/ui/window_headless.png new file mode 100644 index 0000000..598aa47 Binary files /dev/null and b/800OTClient/data/images/ui/window_headless.png differ diff --git a/800OTClient/data/locales/de.lua b/800OTClient/data/locales/de.lua new file mode 100644 index 0000000..9637d66 --- /dev/null +++ b/800OTClient/data/locales/de.lua @@ -0,0 +1,377 @@ + +locale = { + name = "de", + charset = "cp1252", + languageName = "Deutsch", + + formatNumbers = true, + decimalSeperator = ',', + thousandsSeperator = ' ', + + translation = { + ["1a) Offensive Name"] = false, + ["1b) Invalid Name Format"] = false, + ["1c) Unsuitable Name"] = false, + ["1d) Name Inciting Rule Violation"] = false, + ["2a) Offensive Statement"] = false, + ["2b) Spamming"] = false, + ["2c) Illegal Advertising"] = false, + ["2d) Off-Topic Public Statement"] = false, + ["2e) Non-English Public Statement"] = false, + ["2f) Inciting Rule Violation"] = false, + ["3a) Bug Abuse"] = false, + ["3b) Game Weakness Abuse"] = false, + ["3c) Using Unofficial Software to Play"] = false, + ["3d) Hacking"] = false, + ["3e) Multi-Clienting"] = false, + ["3f) Account Trading or Sharing"] = false, + ["4a) Threatening Gamemaster"] = false, + ["4b) Pretending to Have Influence on Rule Enforcement"] = false, + ["4c) False Report to Gamemaster"] = false, + ["Accept"] = "Annehmen", + ["Account name"] = "Benutzername", + ["Account Status:"] = false, + ["Action:"] = false, + ["Add"] = "Hinzufügen", + ["Add new VIP"] = "Neuen Freund hinzufügen", + ["Addon 1"] = "Addon 1", + ["Addon 2"] = "Addon 2", + ["Addon 3"] = "Addon 3", + ["Add to VIP list"] = "Zur VIP Liste hinzufügen", + ["Adjust volume"] = "Lautstärke regeln", + ["Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nSimply click on Ok to resume your journeys!"] = false, + ["All"] = false, + ["All modules and scripts were reloaded."] = "Alle Module wurden neu geladen", + ["Allow auto chase override"] = false, + ["Also known as dash in tibia community, recommended\nfor playing characters with high speed"] = false, + ["Ambient light: %s%%"] = false, + ["Amount:"] = "Menge:", + ["Amount"] = "Menge", + ["Anonymous"] = "Anonym", + ["Are you sure you want to logout?"] = "Sind Sie sicher das du das Spiel verlassen willst?", + ["Attack"] = "Angreifen", + ["Author"] = "Autor", + ["Autoload"] = "Automatisch", + ["Autoload priority"] = "Ladepriorität", + ["Auto login"] = "Automatisch einloggen", + ["Auto login selected character on next charlist load"] = "Automatisches einloggen des ausgewählten Charakters", + ["Axe Fighting"] = "Axtkampf", + ["Balance:"] = "Guthaben:", + ["Banishment"] = false, + ["Banishment + Final Warning"] = false, + ["Battle"] = "Kampf", + ["Browse"] = false, + ["Bug report sent."] = "Bugreport würde versendet.", + ["Button Assign"] = "Button Assign", + ["Buy"] = "Kaufen", + ["Buy Now"] = "Jetzt kaufen", + ["Buy Offers"] = "Angebot", + ["Buy with backpack"] = "Im Backpack kaufen", + ["Cancel"] = "Abbrechen", + ["Cannot login while already in game."] = "Sie können sich nicht einloggen während Sie im Spiel sind.", + ["Cap"] = false, + ["Capacity"] = "Belastbarkeit", + ["Center"] = false, + ["Channels"] = false, + ["Character List"] = "Charakter Liste", + ["Classic control"] = "Klassische Steuerung", + ["Clear current message window"] = "Chatverlauf leeren", + ["Clear Messages"] = false, + ["Clear object"] = "Objekt leeren", + ["Client needs update."] = "Der Client muss geupdated werden.", + ["Close"] = "Schließen", + ["Close this channel"] = "Diesen Channel schließen", + ["Club Fighting"] = "Keulenkampf", + ["Combat Controls"] = "Kampfsteuerungen", + ["Comment:"] = "Kommentar:", + ["Connecting to game server..."] = "Verbindung zum Spielserver wird aufgebaut...", + ["Connecting to login server..."] = "Verbindung zum Loginserver wird aufgebaut...", + ["Console"] = false, + ["Cooldowns"] = false, + ["Copy message"] = "Nachricht kopieren", + ["Copy name"] = "Namen kopieren", + ["Copy Name"] = "Namen kopieren", + ["Create Map Mark"] = false, + ["Create mark"] = false, + ["Create New Offer"] = "Neues Angebot erstellen", + ["Create Offer"] = "Angebot erstellen", + ["Current hotkeys:"] = "Aktuelle Hotkeys", + ["Current hotkey to add: %s"] = "Hotkeys zum hinzufügen: %s", + ["Current Offers"] = "Aktuelle Angebote", + ["Default"] = "Standart", + ["Delete mark"] = false, + ["Description:"] = false, + ["Description"] = "Beschreibung", + ["Destructive Behaviour"] = false, + ["Detail"] = "Details", + ["Details"] = false, + ["Disable Shared Experience"] = "Expteilung deaktivieren", + ["Dismount"] = false, + ["Display connection speed to the server (milliseconds)"] = false, + ["Distance Fighting"] = "Fernkampf", + ["Don't stretch/shrink Game Window"] = false, + ["Edit hotkey text:"] = "Hotkeytext bearbeiten:", + ["Edit List"] = "Liste bearbeiten", + ["Edit Text"] = "Text bearbeiten", + ["Enable music"] = "Musik einschalten", + ["Enable Shared Experience"] = "Expteilung aktivieren", + ["Enable smart walking"] = false, + ["Enable vertical synchronization"] = "'Vertical Synchronization' aktivieren", + ["Enable walk booster"] = false, + ["Enter Game"] = "Dem Spiel beitreten", + ["Enter one name per line."] = "Gib einen Namen pro Zeile ein.", + ["Enter with your account again to update your client."] = false, + ["Error"] = "Error", + ["Error"] = "Error", + ["Excessive Unjustified Player Killing"] = false, + ["Exclude from private chat"] = "Aus dem Privatgespräch ausschließen", + ["Exit"] = false, + ["Experience"] = "Erfahrung", + ["Filter list to match your level"] = false, + ["Filter list to match your vocation"] = false, + ["Find:"] = false, + ["Fishing"] = "Fischen", + ["Fist Fighting"] = "Faustkampf", + ["Follow"] = "Verfolgen", + ["Force Exit"] = false, + ["For Your Information"] = false, + ["Free Account"] = false, + ["Fullscreen"] = "Vollbild", + ["Game"] = false, + ["Game framerate limit: %s"] = false, + ["Graphics"] = "Grafik", + ["Graphics card driver not detected"] = false, + ["Graphics Engine:"] = "Grafikengine:", + ["Head"] = "Kopf", + ["Healing"] = false, + ["Health Info"] = false, + ["Health Information"] = false, + ["Hide monsters"] = "Monster ausblenden", + ["Hide non-skull players"] = "Spieler ohne Skull ausblenden", + ["Hide Npcs"] = "NPCs ausblenden", + ["Hide Offline"] = false, + ["Hide party members"] = "Partymitglieder ausblenden", + ["Hide players"] = "Spieler ausblenden", + ["Hide spells for higher exp. levels"] = false, + ["Hide spells for other vocations"] = false, + ["Hit Points"] = "Lebenspunkte", + ["Hold left mouse button to navigate\nScroll mouse middle button to zoom\nRight mouse button to create map marks"] = false, + ["Hotkeys"] = false, + ["If you shut down the program, your character might stay in the game.\nClick on 'Logout' to ensure that you character leaves the game properly.\nClick on 'Exit' if you want to exit the program without logging out your character."] = "Wenn du das Programm schließt kann es sein, dass dein Charakter im Spiel verweilt.nKlicke 'Logout' um sicherzustellen, dass dein Charakter das Spiel wirklich verlässt.\nKlicke 'Exit' wenn du das Programm beenden willst on deinen Charakter auszuloggen.", + ["Ignore"] = false, + ["Ignore capacity"] = "Belastbarkeit ignorieren", + ["Ignored players:"] = false, + ["Ignore equipped"] = "Equipment ignorieren", + ["Ignore List"] = false, + ["Ignore players"] = false, + ["Ignore Private Messages"] = false, + ["Ignore Yelling"] = false, + ["Interface framerate limit: %s"] = false, + ["Inventory"] = "Inventar", + ["Invite to Party"] = "Zur Party einladen", + ["Invite to private chat"] = "Zum Privatchat einladen", + ["IP Address Banishment"] = false, + ["Item Offers"] = false, + ["It is empty."] = false, + ["Join %s's Party"] = "%ss Party beitreten", + ["Leave Party"] = "Party verlassen", + ["Level"] = "Stufe", + ["Lifetime Premium Account"] = false, + ["Limits FPS to 60"] = "FPS auf 60 begrenzen", + ["List of items that you're able to buy"] = "Liste der Items die du kaufen kannst", + ["List of items that you're able to sell"] = "Liste der Items die du verkaufen kannst", + ["Load"] = "Laden", + ["Logging out..."] = "Ausloggen...", + ["Login"] = "Einloggen", + ["Login Error"] = false, + ["Login Error"] = false, + ["Logout"] = "Ausloggen", + ["Look"] = "Ansehen", + ["Magic Level"] = "Magie Level", + ["Make sure that your client uses\nthe correct game protocol version"] = "Vergewissere dich, dass der Client das richtige Protokoll verwendet.", + ["Mana"] = "Mana", + ["Manage hotkeys:"] = "Hotkeys verwalten:", + ["Market"] = "Markt", + ["Market Offers"] = "Marktangebot", + ["Message of the day"] = "Nachricht des Tages", + ["Message to "] = "Nachricht an ", + ["Message to %s"] = "Nachricht an %s", + ["Minimap"] = "Minimap", + ["Module Manager"] = "Module verwalten", + ["Module name"] = "Modulname", + ["Mount"] = false, + ["Move Stackable Item"] = false, + ["Move up"] = false, + ["My Offers"] = "Mein Angebot", + ["Name:"] = "Name:", + ["Name Report"] = false, + ["Name Report + Banishment"] = false, + ["Name Report + Banishment + Final Warning"] = false, + ["No"] = false, + ["No graphics card detected, everything will be drawn using the CPU,\nthus the performance will be really bad.\nPlease update your graphics driver to have a better performance."] = false, + ["No item selected."] = "Keine Items ausgewählt", + ["No Mount"] = "Kein Reittier", + ["No Outfit"] = "Kein Outfit", + ["No statement has been selected."] = false, + ["Notation"] = false, + ["NPC Trade"] = "NPC Handel", + ["Offer History"] = "Angebotsverlauf", + ["Offers"] = "Angebote", + ["Offer Type:"] = "Angebotstyp:", + ["Offline Training"] = false, + ["Ok"] = false, + ["on %s.\n"] = false, + ["Open"] = "Öffnen", + ["Open a private message channel:"] = "Privatchannel öffnen:", + ["Open charlist automatically when starting client"] = false, + ["Open in new window"] = "Im neuen Fenster öffnen", + ["Open new channel"] = "Neuen Channel öffnen", + ["Options"] = "Optionen", + ["Overview"] = false, + ["Pass Leadership to %s"] = "%s zum Anführer ernennen", + ["Password"] = "Passwort", + ["Piece Price:"] = "Stückpreis", + ["Please enter a character name:"] = "Bitte gib einen Charakternamen an:", + ["Please, press the key you wish to add onto your hotkeys manager"] = "Bitte die gewünschte Taste drücken", + ["Please Select"] = false, + ["Please use this dialog to only report bugs. Do not report rule violations here!"] = false, + ["Please wait"] = "Warte bitte", + ["Port"] = "Port", + ["Position:"] = false, + ["Position: %i %i %i"] = false, + ["Premium Account (%s) days left"] = false, + ["Price:"] = "Preis", + ["Primary"] = "Primär", + ["Protocol"] = "Protokoll", + ["Quest Log"] = false, + ["Randomize"] = false, + ["Randomize characters outfit"] = "Zufälliges Outfit", + ["Reason:"] = "Grund:", + ["Refresh"] = "Aktualisieren", + ["Refresh Offers"] = false, + ["Regeneration Time"] = false, + ["Reject"] = "Ablehnen", + ["Reload All"] = "Alle neu laden", + ["Remember account and password when starts client"] = false, + ["Remember password"] = "Passwort speichern", + ["Remove"] = "Entfernen", + ["Remove %s"] = "%s entfernen", + ["Report Bug"] = false, + ["Reserved for more functionality later."] = false, + ["Reset Market"] = false, + ["Revoke %s's Invitation"] = "%ss Einladung zurückziehen", + ["Rotate"] = "Rotieren", + ["Rule Violation"] = false, + ["Save"] = false, + ["Save Messages"] = false, + ["Search:"] = "Suchen:", + ["Search all items"] = false, + ["Secondary"] = "Sekundär", + ["Select object"] = "Objekt auswählen", + ["Select Outfit"] = "Outfit auswählen", + ["Select your language"] = false, + ["Sell"] = "Verkaufen", + ["Sell Now"] = "Jetzt verkaufen", + ["Sell Offers"] = "Verkaufsangebote", + ["Send"] = "Versenden", + ["Send automatically"] = "Automatisch versenden", + ["Send Message"] = false, + ["Server"] = "Server", + ["Server Log"] = false, + ["Set Outfit"] = "Outfit ändern", + ["Shielding"] = "Verteidigung", + ["Show all items"] = "Alle Items anzeigen", + ["Show connection ping"] = false, + ["Show Depot Only"] = false, + ["Show event messages in console"] = "Event Nachrichten in der Konsole anzeigen", + ["Show frame rate"] = "FPS Rate anzeigen", + ["Show info messages in console"] = "Informations Nachrichten in der Konsole anzeigen", + ["Show left panel"] = false, + ["Show levels in console"] = "Level in der Konsole anzeigen", + ["Show Offline"] = false, + ["Show private messages in console"] = "Privatnachrichten in der Konsole anzeigen", + ["Show private messages on screen"] = "Privatenachrichten auf dem Bildschirm anzeigen", + ["Show Server Messages"] = false, + ["Show status messages in console"] = "Status Nachrichten in der Konsole anzeigen", + ["Show Text"] = "Text anzeigen", + ["Show timestamps in console"] = "Zeit in der Konsole anzeigen", + ["Show your depot items only"] = "Nur Depotitems anzeigen", + ["Skills"] = "Fähigkeiten", + ["Soul"] = false, + ["Soul Points"] = false, + ["Special"] = false, + ["Speed"] = false, + ["Spell Cooldowns"] = false, + ["Spell List"] = false, + ["Stamina"] = "Ausdauer", + ["Statement:"] = false, + ["Statement Report"] = false, + ["Statistics"] = "Statistiken", + ["Stop Attack"] = "Angriff abbrechen", + ["Stop Follow"] = "Verfolgen abbrechen", + ["Support"] = false, + ["%s: (use object)"] = "%s: (Objekt benutzen)", + ["%s: (use object on target)"] = "%s: (Objekt auf Ziel benutzen)", + ["%s: (use object on yourself)"] = false, + ["%s: (use object with crosshair)"] = false, + ["Sword Fighting"] = "Schwertkampf", + ["Terminal"] = "Terminal", + ["There is no way."] = "Es gibt keinen Weg dagin.", + ["Title"] = false, + ["Total Price:"] = "Gesamtpreis:", + ["Trade"] = "Handel", + ["Trade with ..."] = "Handeln mit ...", + ["Trying to reconnect in %s seconds."] = "Versuch neu zu verbinden in %s Sekunden.", + ["Unable to load dat file, please place a valid dat in '%s'"] = false, + ["Unable to load spr file, please place a valid spr in '%s'"] = false, + ["Unable to logout."] = "Es ist nicht möglich auszuloggen.", + ["Unignore"] = false, + ["Unload"] = false, + ["Update needed"] = false, + ["Use"] = "Benutzen", + ["Use on target"] = "Auf Ziel benutzen", + ["Use on yourself"] = false, + ["Use with ..."] = "Benutzen mit ...", + ["Version"] = "Version", + ["VIP List"] = "VIP Liste", + ["Voc."] = false, + ["Vocation"] = false, + ["Waiting List"] = "Warteliste", + ["Website"] = false, + ["Weight:"] = "Gewicht:", + ["Will detect when to use diagonal step based on the\nkeys you are pressing"] = false, + ["With crosshair"] = false, + ["Yes"] = false, + ["You are bleeding"] = "Du blutest", + ["You are burning"] = "Du brennst", + ["You are cursed"] = "Du bist verflucht", + ["You are dazzled"] = "Du bist geblendet", + ["You are dead."] = "Du bist tot.", + ["You are dead"] = "Du bist tot", + ["You are drowning"] = "Du ertrinkst", + ["You are drunk"] = "Du bist betrunken", + ["You are electrified"] = "Du bist elektrifiziert", + ["You are freezing"] = "Du bist am Erfrieren", + ["You are hasted"] = "Du bist am Eilen", + ["You are hungry"] = "Du bist hungrig", + ["You are paralysed"] = "Du bist paralysiert", + ["You are poisoned"] = "Du bist vergiftet", + ["You are protected by a magic shield"] = "Du wirst von einem magischen Schild beschützt", + ["You are strengthened"] = "Du bist gestärkt", + ["You are within a protection zone"] = "Du befindest dich in einer Schutzzone", + ["You can enter new text."] = "Du kannst einen neuen Text eingeben", + ["You have %s percent"] = "Du hast %d Prozent", + ["You have %s percent to go"] = "Dir fehlen %d Prozent", + ["You may not logout during a fight"] = "Du kannst nicht mitten im Kampf ausloggen", + ["You may not logout or enter a protection zone"] = "Du kannst nicht ausloggen oder eine Schutzzone betreten", + ["You must enter a comment."] = "Du musst einen Kommentar eingeben.", + ["You must enter a valid server address and port."] = "Du musst eine gültige Serveradresse und einen gültigen Port eingeben", + ["You must select a character to login!"] = "Du musst einen Charakter auswählen!", + ["Your Capacity:"] = "Deine Belastbarkeit:", + ["You read the following, written by \n%s\n"] = "Du liest das Folgende, geschrieben von \n%s\n", + ["You read the following, written on \n%s.\n"] = false, + ["Your Money:"] = "Dein Geld:", + } +} + +modules.client_locales.installLocale(locale) diff --git a/800OTClient/data/locales/en.lua b/800OTClient/data/locales/en.lua new file mode 100644 index 0000000..5508110 --- /dev/null +++ b/800OTClient/data/locales/en.lua @@ -0,0 +1,14 @@ +locale = { + name = "en", + charset = "cp1252", + languageName = "English", + + formatNumbers = true, + decimalSeperator = '.', + thousandsSeperator = ',', + + -- translations are not needed because everything is already in english + translation = {} +} + +modules.client_locales.installLocale(locale) diff --git a/800OTClient/data/locales/es.lua b/800OTClient/data/locales/es.lua new file mode 100644 index 0000000..0e67deb --- /dev/null +++ b/800OTClient/data/locales/es.lua @@ -0,0 +1,382 @@ +-- special thanks for Shaday, who made these translations +--Dominique120 edits: I made some statements to sound more formal and appropriate as well as correcting a few words that were not translated. I also added a few notes for future translators to keep in mind. + + +locale = { + name = "es", + charset = "cp1252", + languageName = "Español", + + formatNumbers = true, + decimalSeperator = ',', + thousandsSeperator = '.', + + translation = { + ["1a) Offensive Name"] = "1a) Nombre ofensivo", + ["1b) Invalid Name Format"] = "1b) Formato invalido para nombre", + ["1c) Unsuitable Name"] = "1c) Nombre no adecuado", + ["1d) Name Inciting Rule Violation"] = "1d) Nombre que incita una violación al reglamento", + ["2a) Offensive Statement"] = "2a) Comentario ofensivo", + ["2b) Spamming"] = "2b) Spamming", + ["2c) Illegal Advertising"] = "2c) Publicidad ilícita", + ["2d) Off-Topic Public Statement"] = "2d) Publicación fuera de lugar", + ["2e) Non-English Public Statement"] = "2e) Publicación fuera del ingles", + ["2f) Inciting Rule Violation"] = "2f) Incitar a una violación al reglamento", + ["3a) Bug Abuse"] = "3a) Abuso de error", + ["3b) Game Weakness Abuse"] = "3b) Abuso de debilidad del juego", + ["3c) Using Unofficial Software to Play"] = "3c) Usando software ilegal para jugar", + ["3d) Hacking"] = "3d) Hackeo", + ["3e) Multi-Clienting"] = "3e) Uso de múltiples clientes", + ["3f) Account Trading or Sharing"] = "3f) Intercambio de cuenta", + ["4a) Threatening Gamemaster"] = "4a) Amenzar a un Gamemaster", + ["4b) Pretending to Have Influence on Rule Enforcement"] = "4b) Pretender tener influencia en una parte del reglamento", + ["4c) False Report to Gamemaster"] = "4c) Reporte falso a un Gamemaster", + ["Accept"] = "Aceptar", + ["Account name"] = "Nombre de cuenta", + ["Account Status:"] = "Estado de cuenta:", + ["Action:"] = "Acción:", + ["Add"] = "Añadir", + ["Add new VIP"] = "Añadir nuevo VIP", + ["Addon 1"] = "Addon 1", + ["Addon 2"] = "Addon 2", + ["Addon 3"] = "Addon 3", + ["Add to VIP list"] = "Añadir a lista VIP", + ["Adjust volume"] = "Ajustar volumen", + ["Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nSimply click on Ok to resume your journeys!"] = "¡Ay! Aventurero valiente, has conocido un triste destino. \nPero no se desespere, porque los dioses te llevarán de vuelta \na este mundo a cambio de un pequeño sacrificio \n\nSimplemente haga clic en Aceptar para continuar con sus viajes!", + ["All"] = "Todo", + ["All modules and scripts were reloaded."] = "Todos los módulos y scripts se vuelven a cargar.", + ["Allow auto chase override"] = "Permitur auto persecución override", + ["Also known as dash in tibia community, recommended\nfor playing characters with high speed"] = "Conocido por la comunidad de tibia como dash.\nRecomenada para jugadores de alto nivel.", + ["Ambient light: %s%%"] = "Ambiente de luz: %s%%", + ["Amount:"] = "Cantidad:", + ["Amount"] = "Cantidad", + ["Anonymous"] = "Anónimo", + ["Are you sure you want to logout?"] = "¿Estas seguro de que deseas salir?", + ["Attack"] = "Atacar", + ["Author"] = "Autor", + ["Autoload"] = "Auto carga", + ["Autoload priority"] = "Auto carga prioritaria", + ["Auto login"] = "Auto ingresar", + ["Auto login selected character on next charlist load"] = "Ingresar la siguiente vez que aparece el charlist con el personaje seleccionado", + ["Axe Fighting"] = "Combate con hacha", + ["Balance:"] = "Saldo:", + ["Banishment"] = "Banishment", + ["Banishment + Final Warning"] = "Banishment + Final Warning", + ["Battle"] = "Batalla", + ["Browse"] = "Navegar", + ["Bug report sent."] = "Reporte de error enviado.", + ["Button Assign"] = "Botón asignado", + ["Buy"] = "Compra", + ["Buy Now"] = "Compra ahora", + ["Buy Offers"] = "Comprar oferta", + ["Buy with backpack"] = "Comprar con mochila", + ["Cancel"] = "Cancelar", + ["Cannot login while already in game."] = "No se puede iniciar sesión, mientras que estés en el juego.", + ["Cap"] = "Cap", + ["Capacity"] = "Capacidad", + ["Center"] = "Centrar", + ["Channels"] = "Canales", + ["Character List"] = "Lista de carácter", + ["Classic control"] = "Controles Clásicos", + ["Clear current message window"] = "Limpiar mensaje actual en ventana", + ["Clear Messages"] = "Limpiar mensaje", + ["Clear object"] = "Limpiar objeto", + ["Client needs update."] = "El cliente necesita una actualización.", + ["Close"] = "Cerrar", + ["Close this channel"] = "Cerrar este canal", + ["Club Fighting"] = "Combate con mazo", + ["Combat Controls"] = "Controles de combate", + ["Comment:"] = "Comentario:", + ["Connecting to game server..."] = "Conectando a servidor game...", + ["Connecting to login server..."] = "Conectando a servidor login...", + ["Console"] = "Consola", + ["Cooldowns"] = "Descansos", + ["Copy message"] = "Copiar mensaje", + ["Copy name"] = "Copiar nombre", + ["Copy Name"] = "Copiar nombre", + ["Create Map Mark"] = "Crear marca en mapa", + ["Create mark"] = "Crear marca", + ["Create New Offer"] = "Crear nueva oferta", + ["Create Offer"] = "Crear oferta", + ["Current hotkeys:"] = "Actuales hotkeys:", + ["Current hotkey to add: %s"] = "Actuales hotkeys a agregar: %s", + ["Current Offers"] = "Oferta actual", + ["Default"] = "Predeterminado", + ["Delete mark"] = "Borrar Marca", + ["Description:"] = "Descripción:", + ["Description"] = "Descripción", + ["Destructive Behaviour"] = "Comportamiento destructivo", + ["Detail"] = "Detalle", + ["Details"] = "Detalles", + ["Disable Shared Experience"] = "Desactivar experiencia compartida", + ["Dismount"] = "Desmontar", + ["Display connection speed to the server (milliseconds)"] = "Mostrar velocidad de conexión en el servidor (millisegundos)", + ["Distance Fighting"] = "Combate a distancia", + ["Don\'t stretch/shrink Game Window"] = "No estirar ni reducir el tamaño de ventana", + ["Edit hotkey text:"] = "Editar texto de hotkey:", + ["Edit List"] = "Editar lista", + ["Edit Text"] = "Editar texto", + ["Enable music"] = "Habilitar música", + ["Enable Shared Experience"] = "Habilitar experiencia compartida", + ["Enable smart walking"] = "Habilitar caminado inteligente", + ["Enable vertical synchronization"] = "Habilitar sincronización vertical", + ["Enable walk booster"] = "Habilitar caminado turbo", + ["Enter Game"] = "Entrar al juego", + ["Enter one name per line."] = "Introducir un nombre por linea.", + ["Enter with your account again to update your client."] = "Ingrese con su cuenta nuevamente para actualizar el cliente.", + ["Error"] = "Error", + ["Error"] = "Error", + ["Excessive Unjustified Player Killing"] = "Asesinato excesivo injustificado de jugadores", + ["Exclude from private chat"] = "Ejecutar desde un canal privado", + ["Exit"] = "Salir", + ["Experience"] = "Experiencia", + ["Filter list to match your level"] = "Lista de filtros que coincida con el nivel", + ["Filter list to match your vocation"] = "Lista de filtros que coincida con el vocación", + ["Find:"] = "Encontrar:", + ["Fishing"] = "Pesca", + ["Fist Fighting"] = "Combate con puños", + ["Follow"] = "Seguir", + ["Force Exit"] = "Forzar salida", + ["For Your Information"] = "Para tu información", + ["Free Account"] = "Cuenta gratis", + ["Fullscreen"] = "Pantalla completa", + ["Game"] = "Juego", + ["Game framerate limit: %s"] = "Limite de cuadros por segundo en el juego: %s", + ["Graphics"] = "Gráficos", + ["Graphics card driver not detected"] = "Controlador de tarjeta gráfica de video no detectado", + ["Graphics Engine:"] = "Motor Gráfico:", + ["Head"] = "Cabeza", + ["Healing"] = "Curación", + ["Health Info"] = "HP Info",--This can be better + ["Health Information"] = "HP Información",--This can be better + ["Hide monsters"] = "Ocultar monstruos", + ["Hide non-skull players"] = "Ocultar jugadores sin skull", + ["Hide Npcs"] = "Ocultar NPCs", + ["Hide Offline"] = "Ocultar fuera de linea", + ["Hide party members"] = "Ocultar miembros del party", + ["Hide players"] = "Ocultar players", + ["Hide spells for higher exp. levels"] = "Ocultar hechizos para niveles mas altos que tu experiencia.", + ["Hide spells for other vocations"] = "Ocultar hechizos que sean para otra vocación", + ["Hit Points"] = "Puntos de vida", + ["Hold left mouse button to navigate\nScroll mouse middle button to zoom\nRight mouse button to create map marks"] = "Mantenga presionado el botón derecho del ratón para navegar\nDezplaze la rueda central del ratón para ampliar\nbotón derecho del mouse para crear marcas del mapa", + ["Hotkeys"] = "Hotkeys", + ["If you shut down the program, your character might stay in the game.\nClick on 'Logout' to ensure that you character leaves the game properly.\nClick on 'Exit' if you want to exit the program without logging out your character."] = "Si se cierra el programa, tu personaje puede permanecer en el juego.\nHaga clic en 'Salir' para asegurarse de que personaje deja el juego correctamente.\nHaga click en 'Salir' si desea salir del programa sin tener que salir de tu personaje.", + ["Ignore"] = "Ignorar", + ["Ignore capacity"] = "Ignorar Capacidad", + ["Ignored players:"] = "Jugadores ignorados:", + ["Ignore equipped"] = "Ignorar lo equipado", + ["Ignore List"] = "Ignorar lista", + ["Ignore players"] = "Ignorar jugadores", + ["Ignore Private Messages"] = "Ignorar mensajes privados", + ["Ignore Yelling"] = "Ignorar gritos", + ["Interface framerate limit: %s"] = "Interface de cuadros por segundo: %s", + ["Inventory"] = "Inventario", + ["Invite to Party"] = "Ivitar al party", + ["Invite to private chat"] = "Invitar a canal privado", + ["IP Address Banishment"] = "Banishment - Dirección IP", + ["Item Offers"] = "Ofertas de objetos", + ["It is empty."] = "Está vació.", + ["Join %s\'s Party"] = "Unirse al party de %s ", + ["Leave Party"] = "Dejar el party", + ["Level"] = "Nivel", + ["Lifetime Premium Account"] = "Tiempo de Premium Account infinito", + ["Limits FPS to 60"] = "Limites FPS a 60", + ["List of items that you're able to buy"] = "Lista de objetos que puedes de comprar", + ["List of items that you're able to sell"] = "Lista de objetos que puedes de vender", + ["Load"] = "Cargar", + ["Logging out..."] = "Cerrando sesión...", + ["Login"] = "Ingresar", + ["Login Error"] = "Error de ingreso", + ["Login Error"] = "Error de ingreso", + ["Logout"] = "Salir", + ["Look"] = "Mirar", + ["Magic Level"] = "Nivel mágico", + ["Make sure that your client uses\nthe correct game protocol version"] = "Asegúrese de que el cliente este utilizando\nes el versión del protocolo adecuado", + ["Mana"] = "Mana", + ["Manage hotkeys:"] = "Administrador de hotkeys:", + ["Market"] = "Mercado", + ["Market Offers"] = "Ofertas en mercado", + ["Message of the day"] = "Mensaje del día", + ["Message to "] = "Mensaje a", + ["Message to %s"] = "Mensaje a %s", + ["Minimap"] = "Minimapa", + ["Module Manager"] = "Administrador de módulos", + ["Module name"] = "Nombre del modulo", + ["Mount"] = "Montar", --Unique name? + ["Move Stackable Item"] = "Mover objeto apilable", + ["Move up"] = "Mover arriba", + ["My Offers"] = "Mis ofertas", + ["Name:"] = "Nombre:", + ["Name Report"] = "Name Report", + ["Name Report + Banishment"] = "Name Report + Banishment", + ["Name Report + Banishment + Final Warning"] = "Name Report + Banishment + Final Warning", + ["No"] = "No", + ["No graphics card detected, everything will be drawn using the CPU,\nthus the performance will be really bad.\nPlease update your graphics driver to have a better performance."] = "No se ha detectado una tarjeta gráfica y todo sera procesado por tu procesador,\npor lo tanto el rendimiento va a ser muy malo.\nPor favor, actualice su controlador de gráficos para tener un rendimiento optimo.", + ["No item selected."] = "No hay elemento seleccionado.", + ["No Mount"] = "No montura", + ["No Outfit"] = "No outfit", + ["No statement has been selected."] = "No hay comentario seleccionado.", + ["Notation"] = "Notation", + ["NPC Trade"] = "Intercambio con NPC", + ["Offer History"] = "Historial de oferta", + ["Offers"] = "Ofertas", + ["Offer Type:"] = "Tipo de oferta:", + ["Offline Training"] = "Entrenamiento offLine", + ["Ok"] = "OK", + ["on %s.\n"] = "en %s.\n", + ["Open"] = "Abierto", + ["Open a private message channel:"] = "Abrir mensaje en canal privado:", + ["Open charlist automatically when starting client"] = "Abrir lista de jugadores automáticamente al iniciar el cliente", + ["Open in new window"] = "Abrir en nueva ventana", + ["Open new channel"] = "Abrir nuevo canal", + ["Options"] = "Opciones", + ["Overview"] = "Descripción", + ["Pass Leadership to %s"] = "Pasar liderazgo a %s", + ["Password"] = "Contraseña", + ["Piece Price:"] = "Precio por pieza:", + ["Please enter a character name:"] = "Por favor ingresar nombre del jugador:", + ["Please, press the key you wish to add onto your hotkeys manager"] = "Por favor, presiona la tecla que desees para que sea registrada en\nel administrador de hotkeys", + ["Please Select"] = "Por favor seleccione", + ["Please use this dialog to only report bugs. Do not report rule violations here!"] = "Por favor usa este diálogo solo para reportar errores.\n¡No reportar violaciones del reglamento aquí!", + ["Please wait"] = "Por favor espere", + ["Port"] = "Puerto", + ["Position:"] = "Posición:", + ["Position: %i %i %i"] = "Posición: %i %i %i", + ["Premium Account (%s) days left"] = "Tienes (%s) días de Premium Account restantes", + ["Price:"] = "Precio:", + ["Primary"] = "Primario", + ["Protocol"] = "Protocolo", + ["Quest Log"] = "Quest Log", --Unique name + ["Randomize"] = "Combinar", + ["Randomize characters outfit"] = "Combinar vestimenta del jugador", + ["Reason:"] = "Razón:", + ["Refresh"] = "Refrescar", + ["Refresh Offers"] = "Refrescar ofertas", + ["Regeneration Time"] = "Tiempo de regeneración", + ["Reject"] = "Rechazar", + ["Reload All"] = "Cargar todo de nuevo", + ["Remember account and password when starts client"] = "Recordar cuenta y contraseña al iniciar el cliente", + ["Remember password"] = "Recordar contraseña", + ["Remove"] = "Remover", + ["Remove %s"] = "Remover %s", + ["Report Bug"] = "Reportar error", + ["Reserved for more functionality later."] = "Reservado para una función futura.", + ["Reset Market"] = "Reiniciar mercado", + ["Revoke %s\'s Invitation"] = "Anular %s\'s invitación", + ["Rotate"] = "Rotar", + ["Rule Violation"] = "Violación del reglamento", + ["Save"] = "Guardar", + ["Save Messages"] = "Guardar mensaje", + ["Search:"] = "Buscar:", + ["Search all items"] = "Buscar todos los objetos", + ["Secondary"] = "Secundario", + ["Select object"] = "Seleccionar objeto", + ["Select Outfit"] = "Seleccionar outfit", + ["Select your language"] = "Selectionar tu lenguaje", + ["Sell"] = "Vender", + ["Sell Now"] = "Vender ahora", + ["Sell Offers"] = "Ofertas de venta", + ["Send"] = "Enviar", + ["Send automatically"] = "Enviar automáticamente", + ["Send Message"] = "Enviar mensaje", + ["Server"] = "Server", --Unique name + ["Server Log"] = "Server Log", --Unique name + ["Set Outfit"] = "Escoger vestimenta", + ["Shielding"] = "Escudo", + ["Show all items"] = "Mostrar todos los objetos", + ["Show connection ping"] = "Mostrar ping de conexión", + ["Show Depot Only"] = "Mostrar solo el Depot", + ["Show event messages in console"] = "Mostrar mensajes de evento en consola", + ["Show frame rate"] = "Mostrar información de cuadros por secundo", + ["Show info messages in console"] = "Mostrar mensajes de información en consola", + ["Show left panel"] = "Mostrar panel izquierdo", + ["Show levels in console"] = "Mostrar niveles en consola", + ["Show Offline"] = "Mostrar Desconectados", + ["Show private messages in console"] = "Mostrar mensajes privados en consola", + ["Show private messages on screen"] = "Mostrar mensajes privados en pantalla", + ["Show Server Messages"] = "Mostrar mensajes del servidor", + ["Show status messages in console"] = "Mostrar mensajes de estado en consola", + ["Show Text"] = "Mostrar texto", + ["Show timestamps in console"] = "Mostrar marcas de tiempo en consola", + ["Show your depot items only"] = "Mostrar solo tus objetos en depot", + ["Skills"] = "Habilidades", + ["Soul"] = "Soul", + ["Soul Points"] = "Puntos de Soul", --I'm leaving these as is because its a unique name, if you want to change it it can be "Alma" or "Espíritu" + ["Special"] = "Especial", + ["Speed"] = "Velocidad", + ["Spell Cooldowns"] = "Spells Cooldowns", --Could be "Tiempo de recarga para los hechizos". + ["Spell List"] = "Lista de hechizos", + ["Stamina"] = "Resistencia", + ["Statement:"] = "Comentario:", + ["Statement Report"] = "Statement Report", --Could be "reporte del comentario" + ["Statistics"] = "Estadísticas", + ["Stop Attack"] = "Detener ataque", + ["Stop Follow"] = "Detener persecución", + ["Support"] = "Soporte", + ["%s: (use object)"] = "%s: (usar objeto)", + ["%s: (use object on target)"] = "%s: (usar objeto en un objetivo)", + ["%s: (use object on yourself)"] = "%s: (usar objeto en mi mismo)", + ["%s: (use object with crosshair)"] = "%s: (usar objeto con punto de mira)", + ["Sword Fighting"] = "Combate de espada", + ["Terminal"] = "Terminal", + ["There is no way."] = "No hay ninguna manera.", + ["Title"] = "Titulo", + ["Total Price:"] = "Total total:", + ["Trade"] = "Intercambio", + ["Trade with ..."] = "Intercambiar con ...", + ["Trying to reconnect in %s seconds."] = "", + ["Unable to load dat file, please place a valid dat in '%s'"] = "No se puede cargar el archivo dat, por favor coloque un dat válido en '%s'", + ["Unable to load spr file, please place a valid spr in '%s'"] = "No se puede cargar el archivo spr, por favor coloque un spr válido en '%s'", + ["Unable to logout."] = "No se puede cerrar sesión-", + ["Unignore"] = "Dejar de ignorar", + ["Unload"] = "No cargado", + ["Update needed"] = "Es necesario actualizar", + ["Use"] = "Uso", + ["Use on target"] = "Usar en un objetivo", + ["Use on yourself"] = "Usar en mi mismo", + ["Use with ..."] = "Usar en ...", + ["Version"] = "Versión", + ["VIP List"] = "Lista Vip", + ["Voc."] = "Voc.", + ["Vocation"] = "Vocación", + ["Waiting List"] = "Lista de espera", + ["Website"] = "Sitio Web", + ["Weight:"] = "Peso:", + ["Will detect when to use diagonal step based on the\nkeys you are pressing"] = "Detectara cuando se camina en diagonal usando las flechas", + ["With crosshair"] = "Con punto de mira", + ["Yes"] = "Si", + ["You are bleeding"] = "Te estas desangrando", + ["You are burning"] = "Te estas quemando", + ["You are cursed"] = "Tu estas maldecido", + ["You are dazzled"] = "Tu estas deslumbrado", + ["You are dead."] = "Tu estas muerto.", + ["You are dead"] = "Tu estas muerto", + ["You are drowning"] = "Te estas ahogando", + ["You are drunk"] = "Tu estas ebrio", + ["You are electrified"] = "Tu estas electrocutado", + ["You are freezing"] = "Te estas congelando", + ["You are hasted"] = "Tu estas rápido", --I dont know what is the best way to translate this so I'm leaving it as I found it. + ["You are hungry"] = "Tu estas hambriento", + ["You are paralysed"] = "Tu estas paralizado", + ["You are poisoned"] = "Tu estas envenedado", + ["You are protected by a magic shield"] = "Tu estas protegido por un escudo mágico", + ["You are strengthened"] = "Tu estas reforzado", + ["You are within a protection zone"] = "Tu estas dentro de una zona de protección", + ["You can enter new text."] = "Tu puedes ingresar un texto nuevo.", + ["You have %s percent"] = "Tu tienes %s por ciento", + ["You have %s percent to go"] = "Tu tienes %s por ciento para ir", + ["You may not logout during a fight"] = "No puedes salir durante una pelea", + ["You may not logout or enter a protection zone"] = "No puedes salir o entrar en una zona de protección", + ["You must enter a comment."] = "Debes ingresar un comentario.", + ["You must enter a valid server address and port."] = "Debes ingresar una dirección válida de servidor y el puerto.", + ["You must select a character to login!"] = "¡Debes seleccionar un personaje para ingresar!", + ["Your Capacity:"] = "Tu capacidad:", + ["You read the following, written by \n%s\n"] = "Lees lo siguiente, escrito por \n%s\n", + ["You read the following, written on \n%s.\n"] = "Lees lo siguiente, escrito en \n%s\n", + ["Your Money:"] = "Tu dinero:", + ["Change language"] = "Cambiar idioma", + ["Don't stretch or shrink Game Window"] = "No estirar o encoger Ventana de Juego" + } +} + +modules.client_locales.installLocale(locale) \ No newline at end of file diff --git a/800OTClient/data/locales/pl.lua b/800OTClient/data/locales/pl.lua new file mode 100644 index 0000000..a7c3813 --- /dev/null +++ b/800OTClient/data/locales/pl.lua @@ -0,0 +1,419 @@ +locale = { + name = "pl", + languageName = "Polski", + + formatNumbers = true, + decimalSeperator = '.', + thousandsSeperator = ' ', + + translation = { + ["1a) Offensive Name"] = "1a) Obrazliwe Imie", + ["1b) Invalid Name Format"] = "1b) Niepoprawny Format Imienia", + ["1c) Unsuitable Name"] = "1c) Nieodpowiednie Imie", + ["1d) Name Inciting Rule Violation"] = "1d) Imie Nawolujace Do Lamania Regulaminu", + ["2a) Offensive Statement"] = "2a) Obrazliwa wypowiedz", + ["2b) Spamming"] = "2b) Spamowanie", + ["2c) Illegal Advertising"] = "2c) Nielegalne Reklamy", + ["2d) Off-Topic Public Statement"] = "2d) Publiczne Wypowiadanie Sie Nie Na Temat", + ["2e) Non-English Public Statement"] = "2e) Publiczne wypowiadanie Sie W Jezyku Innym Niz Angielski", + ["2f) Inciting Rule Violation"] = "2f) Nawolywanie Do Lamania Regulaminu ", + ["3a) Bug Abuse"] = "3a) Wykorzystywanie Bledow", + ["3b) Game Weakness Abuse"] = "3b) Wykorzystanie Slabosci Gry", + ["3c) Using Unofficial Software to Play"] = "3c) Gra Przy Uzyciu Nieoficjalnego Oprogramowania", + ["3d) Hacking"] = "3d) Wlamywanie Sie", + ["3e) Multi-Clienting"] = "3e) Uzycie Multi-Klienta", + ["3f) Account Trading or Sharing"] = "3f) Handel Lub Udostepnianie Kont", + ["4a) Threatening Gamemaster"] = "4a) Grozby Pod Adresem Mistrza Gry", + ["4b) Pretending to Have Influence on Rule Enforcement"] = "4b) Udawanie Wplywu na Ustanawianie Regul Gry", + ["4c) False Report to Gamemaster"] = "4c) Wyslanie Falszywego Raportu Mistrzowi Gry", + ["Accept"] = "Akceptuj", + ["Account name"] = "Numer konta", + ["Account Status:"] = "Status Konta:", + ["Action:"] = "Akcja:", + ["Add"] = "Dodaj", + ["Add new VIP"] = "Nowy VIP", + ["Addon 1"] = "Addon 1", + ["Addon 2"] = "Addon 2", + ["Addon 3"] = "Addon 3", + ["Add to VIP list"] = "Dodaj do VIPow", + ["Adjust volume"] = "Zmien glosnosc", + ["Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nSimply click on Ok to resume your journeys!"] = false, + ["All"] = "Wszystkie", + ["All modules and scripts were reloaded."] = "Wszystkie moduly ", + ["Allow auto chase override"] = false, + ["Also known as dash in tibia community, recommended\nfor playing characters with high speed"] = false, + ["Ambient light: %s%%"] = "Swiatlo tla: %s%%", + ["Amount:"] = "Ilosc:", + ["Amount"] = "Ilosc", + ["Anonymous"] = "Anonimowy", + ["Are you sure you want to logout?"] = "Czy jestes pewien, ze sie chcesz wylogowac?", + ["Attack"] = "Atak", + ["Author"] = "Autor", + ["Autoload"] = "Autoladowanie", + ["Autoload priority"] = "Priorytet autoladowania", + ["Auto login"] = "Loguj automatycznie", + ["Auto login selected character on next charlist load"] = "Automatycznie zaloguj wybrana postac podczas kolejnego ladowaia listy postaci", + ["Axe Fighting"] = "Walka toporem", + ["Balance:"] = false, + ["Banishment"] = false, + ["Banishment + Final Warning"] = false, + ["Battle"] = "Bitwa", + ["Browse"] = "Przegladaj", + ["Bug report sent."] = "Raport o bledzie zostal wyslany.", + ["Button Assign"] = "Przypisanie Klawisza", + ["Buy"] = "Kup", + ["Buy Now"] = "Kup Teraz", + ["Buy Offers"] = "Oferty Kupna", + ["Buy with backpack"] = "Kupuj z plecakami", + ["Cancel"] = "Anuluj", + ["Cannot login while already in game."] = "Nie mozna zalogowac gdy juz w grze", + ["Cap"] = "Ladownosc", + ["Capacity"] = "Ladownosc", + ["Center"] = "Wysrodkuj", + ["Channels"] = "Kanaly", + ["Character List"] = "Lista postaci", + ["Classic control"] = "Klasyczne sterowaie", + ["Clear current message window"] = "Wyczysc bierzace okno", + ["Clear Messages"] = "Wyczysc wiadomosci", + ["Clear object"] = "Wyczysc obiekt", + ["Client needs update."] = "Klient wymaga aktalizacji", + ["Close"] = "Zamknij", + ["Close this channel"] = "Zamknij kanal", + ["Club Fighting"] = "Walka obuchem", + ["Combat Controls"] = "Kontrola walki", + ["Comment:"] = "Komentarz:", + ["Connecting to game server..."] = "Laczenie z serwerem gry...", + ["Connecting to login server..."] = "Laczenie z serwerem logowania...", + ["Console"] = "Konsola", + ["Cooldowns"] = "Czasy odnowienia", + ["Copy message"] = "Kopiuj wiadomosc", + ["Copy name"] = "Kopiuj imie", + ["Copy Name"] = "Kopiuj Imie", + ["Create Map Mark"] = "Utworz Znacznik na Mapie", + ["Create mark"] = "Utworz znacznik", + ["Create New Offer"] = "Utworz Nowa Oferte", + ["Create Offer"] = "Utworz Oferte", + ["Current hotkeys:"] = "Aktualny hotkey:", + ["Current hotkey to add: %s"] = "Aktualny hotkey do dodania: %s", + ["Current Offers"] = "Obecne Oferty", + ["Default"] = "Domyslny", + ["Delete mark"] = "Usun znacznik", + ["Description:"] = "Opis:", + ["Description"] = "Opis", + ["Destructive Behaviour"] = "Destrukcyjne Zachowanie", + ["Detail"] = "Szczegoly", + ["Details"] = "Szczegoly", + ["Disable Shared Experience"] = "Wylacz Dzielenie Doswiadczenia", + ["Dismount"] = "Zejdz z wierzchowca", + ["Display connection speed to the server (milliseconds)"] = "Wyswietl ping do serwera (ms)", + ["Distance Fighting"] = "Walka na odleglosc", + ["Don't stretch/shrink Game Window"] = "Nie rozszerzaj/zwezaj Okna Gry", + ["Edit hotkey text:"] = "Edytuj tresc hotkeya:", + ["Edit List"] = "Lista Edycji", + ["Edit Text"] = "Edytuj tekst", + ["Enable music"] = "Odtwarzaj muzyke", + ["Enable Shared Experience"] = "Wlacz dzielenie doswiadczenia", + ["Enable smart walking"] = "Wlacz inteligentne chodzenie", + ["Enable vertical synchronization"] = "Wlacz synchronizacje pionowa", + ["Enable walk booster"] = false, + ["Enter Game"] = "Wejdz do gry", + ["Enter one name per line."] = "Wprowadz jedno imie na linie", + ["Enter with your account again to update your client."] = "Zaloguj sie ponownie by zaktualizowac klienta", + ["Error"] = "Blad", + ["Excessive Unjustified Player Killing"] = "Nadmierne Nieusprawiedliwione Zabijanie Graczy", + ["Exclude from private chat"] = "Wyrzuc w prywatnej konwersacji", + ["Exit"] = "Wyjdz", + ["Experience"] = "Doswiadczenie", + ["Filter list to match your level"] = "Wyswietl tylko odpowiednie dla mojego poziomu", + ["Filter list to match your vocation"] = "Wyswietl tylko odpowiednie dla mojej klasy", + ["Find:"] = "Szukaj:", + ["Fishing"] = "Wedkarstwo", + ["Fist Fighting"] = "Walka wrecz", + ["Follow"] = "Podazaj", + ["Force Exit"] = "Wymus Zamkniecie", + ["For Your Information"] = "Dla twojej informacji", + ["Free Account"] = "Darmowe Konto", + ["Fullscreen"] = "Pelen ekran", + ["Game"] = "Gra", + ["Game framerate limit: %s"] = "Limit FPS: %s", + ["Graphics"] = "Grafika", + ["Graphics card driver not detected"] = "Nie wykryto karty graficznej", + ["Graphics Engine:"] = "Silnik graficzny:", + ["Head"] = "Glowa", + ["Healing"] = "Leczenie", + ["Health Info"] = "Info o zyciu", + ["Health Information"] = "Informacje o zyciu", + ["Hide monsters"] = "Ukryj potwory", + ["Hide non-skull players"] = "Ukryj graczy bez skulla", + ["Hide Npcs"] = "Ukryj NPCe", + ["Hide Offline"] = "Ukryj Niedostepnych", + ["Hide party members"] = "Ukryj czlonkow druzyny", + ["Hide players"] = "Ukryj graczy", + ["Hide spells for higher exp. levels"] = "Ukryj zaklecia wyzszych poziomow postaci", + ["Hide spells for other vocations"] = "Ukryj zaklecia innych klas", + ["Hit Points"] = "Punkty uderzen", + ["Hold left mouse button to navigate\nScroll mouse middle button to zoom\nRight mouse button to create map marks"] = false, + ["Hotkeys"] = "Hotkeye", + ["If you shut down the program, your character might stay in the game.\nClick on 'Logout' to ensure that you character leaves the game properly.\nClick on 'Exit' if you want to exit the program without logging out your character."] = false, + ["Ignore"] = "Ignoruj", + ["Ignore capacity"] = "Ignoruj pojemnosc", + ["Ignored players:"] = "Ignorowani gracze:", + ["Ignore equipped"] = "Ignoruj ekwipunek", + ["Ignore List"] = "Lista Ignorowanych", + ["Ignore players"] = "Ignoruj graczy", + ["Ignore Private Messages"] = "Ignoruj Prywatne Wiadomosci", + ["Ignore Yelling"] = "Ignoruj Krzyki", + ["Interface framerate limit: %s"] = "Limit FPS interfejsu: %s", + ["Inventory"] = "Ekwipunek", + ["Invite to Party"] = "Zapros do druzyny", + ["Invite to private chat"] = "Zapros do prywatnej konwersacji", + ["IP Address Banishment"] = "Blokada adresu IP", + ["Item Offers"] = "Oferty Przedmiotow", + ["It is empty."] = "To jest puste.", + ["Join %s's Party"] = "Dolacz do druzyny gracza %s", + ["Leave Party"] = "Opusc druzyne", + ["Level"] = "Poziom", + ["Lifetime Premium Account"] = "Konto Premium na Stale", + ["Limits FPS to 60"] = "Ogranicz FPS do 60", + ["List of items that you're able to buy"] = "Lista przedmiotow, ktore mozesz kupic", + ["List of items that you're able to sell"] = "Lista przedmiotow, ktore mozesz sprzedac", + ["Load"] = "Wczytaj", + ["Logging out..."] = "Wylogowuje...", + ["Login"] = "Zaloguj", + ["Login Error"] = "Blad Logowania", + ["Login Error"] = "Blad Logowania", + ["Logout"] = "Wyloguj", + ["Look"] = "Spojrz", + ["Magic Level"] = "Poziom Magiczny", + ["Make sure that your client uses\nthe correct game protocol version"] = "Upewnij sie, ze twoj klient\nuzywa wlasciwego protokolu gry.", + ["Mana"] = "Mana", + ["Manage hotkeys:"] = "Zarzadzaj hotkeyami:", + ["Market"] = false, + ["Market Offers"] = "Oferty", + ["Message of the day"] = "Wiadomosc dnia", + ["Message to "] = "Wiadomosc do ", + ["Message to %s"] = "Wiadomosc do %s", + ["Minimap"] = "Minimapa", + ["Module Manager"] = "Menedzer modulow", + ["Module name"] = "Nazwa modulu", + ["Mount"] = "Wierzchowiec", + ["Move Stackable Item"] = "Przenies przedmiot", + ["Move up"] = "Przenies wyzej", + ["My Offers"] = "Moje Oferty", + ["Name:"] = "Nazwa:", + ["Name Report"] = false, + ["Name Report + Banishment"] = false, + ["Name Report + Banishment + Final Warning"] = false, + ["No"] = "Nie", + ["No graphics card detected, everything will be drawn using the CPU,\nthus the performance will be really bad.\nPlease update your graphics driver to have a better performance."] = false, + ["No item selected."] = "Nie wybrano przedmiotu.", + ["No Mount"] = "Brak wierzchowca", + ["No Outfit"] = "Brak stroju", + ["No statement has been selected."] = false, + ["Notation"] = false, + ["NPC Trade"] = "Handel NPC", + ["Offer History"] = "Historia Ofert", + ["Offers"] = "Oferty", + ["Offer Type:"] = "Typ Oferty:", + ["Offline Training"] = "Trening Offline", + ["Ok"] = "Ok", + ["on %s.\n"] = false, + ["Open"] = "Otworz", + ["Open a private message channel:"] = "Otworz prywatny kanal:", + ["Open charlist automatically when starting client"] = "Automatycznie otworz liste postaci przy starcie gry", + ["Open in new window"] = "Otworz w nowym oknie", + ["Open new channel"] = "Otworz nowy kanal", + ["Options"] = "Opcje", + ["Overview"] = "Podsumowanie", + ["Pass Leadership to %s"] = "Oddaj przywodztwo %s", + ["Password"] = "Haslo", + ["Piece Price:"] = "Cena jednego przedmiotu", + ["Please enter a character name:"] = "Podaj nazwe postaci:", + ["Please, press the key you wish to add onto your hotkeys manager"] = "Nacisnij klawisz, ktory chcesz dodac do menedzera skrotow klawiszowych", + ["Please Select"] = "Prosze wybrac", + ["Please use this dialog to only report bugs. Do not report rule violations here!"] = "Zglaszaj tylko bledy gry, nie lamanie zasad", + ["Please wait"] = "Prosze czekac", + ["Port"] = "Port", + ["Position:"] = "Pozycja:", + ["Position: %i %i %i"] = "Pozycja: %i %i %i", + ["Premium Account (%s) days left"] = "Konto Premium (%s) dni", + ["Price:"] = "Cena:", + ["Primary"] = "Podstawowy", + ["Protocol"] = "Protokol", + ["Quest Log"] = "Dziennik Misji", + ["Randomize"] = "Losuj", + ["Randomize characters outfit"] = "Ustaw losowy wyglad", + ["Reason:"] = "Powod:", + ["Refresh"] = "Odswiez", + ["Refresh Offers"] = "Odswiez Oferty", + ["Regeneration Time"] = "Czas Regeneracji", + ["Reject"] = "Odrzuc", + ["Reload All"] = "Zaladuj ponownie wszystko", + ["Remember account and password when starts client"] = "Zapamietaj identyfikator konta oraz haslo", + ["Remember password"] = "Zapamietaj haslo", + ["Remove"] = "Usun", + ["Remove %s"] = "Usun %s", + ["Report Bug"] = "Zglos Blad", + ["Reserved for more functionality later."] = "Zarezerowane dla przyszlych funkcjonalnosci.", + ["Reset Market"] = "Zaladuj market ponownie", + ["Revoke %s's Invitation"] = "Odmow na zaproszenie gracza %s", + ["Rotate"] = "Obroc", + ["Rule Violation"] = "Zlamanie Regul", + ["Save"] = "Zapisz", + ["Save Messages"] = "Zapisz Wiadomosci", + ["Search:"] = "Szukaj:", + ["Search all items"] = "Znajdz wszystkie przedmioty", + ["Secondary"] = "Drugorzedny", + ["Select object"] = "Wybierz obiekt", + ["Select Outfit"] = "Wybierz outfit", + ["Select your language"] = "Wybierz jezyk", + ["Sell"] = "Sprzedaj", + ["Sell Now"] = "Sprzedaj Teraz", + ["Sell Offers"] = "Oferty Sprzedazy", + ["Send"] = "Wyslij", + ["Send automatically"] = "Wyslij automatycznie", + ["Send Message"] = "Wyslij Wiadomosc", + ["Server"] = "Serwer", + ["Server Log"] = "Log Serwera", + ["Set Outfit"] = "Ustaw outfit", + ["Shielding"] = "Obrona tarcza", + ["Show all items"] = "Pokaz wszystkie przedmioty", + ["Show connection ping"] = "Wyswietl ping", + ["Show Depot Only"] = "Pokaz tylko przedmioty z depozytu", + ["Show event messages in console"] = "Pokaz wydarzenia w konsoli", + ["Show frame rate"] = "Pokaz FPS", + ["Show info messages in console"] = "Pokaz informacje w konsoli", + ["Show left panel"] = "Pokaz lewy panel", + ["Show levels in console"] = "Pokaz poziomy w konsoli", + ["Show Offline"] = "Pokaz Niedostepnych", + ["Show private messages in console"] = "Pokaz prywatne wiadomosci w konsoli", + ["Show private messages on screen"] = "Pokaz prywatne wiadomosci na ekranie", + ["Show Server Messages"] = "Pokaz Wiadomosci Serwera", + ["Show status messages in console"] = "Pokaz status w konsoli", + ["Show Text"] = "Pokaz Tekst", + ["Show timestamps in console"] = "Pokaz znaczniki czasu w konsoli", + ["Show your depot items only"] = "Pokaz tylko przedmioty z depozytu", + ["Skills"] = "Umiejetnosci", + ["Soul"] = "Dusze", + ["Soul Points"] = "Punktey Duszy", + ["Special"] = "Specialne", + ["Speed"] = "Predkosc", + ["Spell Cooldowns"] = "Czas odnowienia czaru", + ["Spell List"] = "Lista Zaklec", + ["Stamina"] = "Wytrzymalosc", + ["Statement:"] = "Wyrazenie", + ["Statement Report"] = "Reportuj wyrazenie", + ["Statistics"] = "Statystki", + ["Stop Attack"] = "Anuluj atak", + ["Stop Follow"] = "Przestan podazac", + ["Support"] = "Wsparcie", + ["%s: (use object)"] = "%s: (uzyj obiekt)", + ["%s: (use object on target)"] = "%s: (uzyj obiektu na celu)", + ["%s: (use object on yourself)"] = "%s: (uzyj obiektu na sobie)", + ["%s: (use object with crosshair)"] = "%s: (uzyj obiektu z celownikiem)", + ["Sword Fighting"] = "Atak mieczem", + ["Terminal"] = "Terminal", + ["There is no way."] = "Nie ma drogi.", + ["Title"] = "Tytul", + ["Total Price"] = "Cena calosci", + ["Trade"] = "Handel", + ["Trade with ..."] = "Handluj z ...", + ["Trying to reconnect in %s seconds."] = "Ponowna proba laczenia za %s sekund.", + ["Unable to load dat file, please place a valid dat in '%s'"] = "Nie mozna zaladowac pliku .dat z '%s'", + ["Unable to load spr file, please place a valid spr in '%s'"] = "Nie mozna zaladowac pliku .spr z '%s'", + ["Unable to logout."] = "Nie mozna sie wylogowac.", + ["Unignore"] = "Anuluj Ignorowanie", + ["Unload"] = "Wylacz", + ["Update needed"] = "Wymagana aktualizacja", + ["Use"] = "Uzyj", + ["Use on target"] = "Uzyj na celu", + ["Use on yourself"] = "Uzyj na sobie", + ["Use with ..."] = "Uzyj z ...", + ["Version"] = "Wersja", + ["VIP List"] = "Lista VIP", + ["Voc."] = "Profesja", + ["Vocation"] = "Profesja", + ["Waiting List"] = "Lista Oczekujacych", + ["Website"] = "Strona:", + ["Weight:"] = "Waga:", + ["Will detect when to use diagonal step based on the\nkeys you are pressing"] = false, + ["With crosshair"] = "Z celownikiem", + ["Yes"] = "Tak", + ["You are bleeding"] = "Krwawisz", + ["You are burning"] = "Palisz sie", + ["You are cursed"] = "Jestes przeklety", + ["You are dazzled"] = "Jestes oslepiony", + ["You are dead."] = "Jestes martwy.", + ["You are dead"] = "Jestes martwy", + ["You are drowning"] = "Topisz sie", + ["You are drunk"] = "Jestes pijany", + ["You are electrified"] = "Jestes porazony pradem", + ["You are freezing"] = "Zamarzasz", + ["You are hasted"] = "Masz zwiekszona predkosc ruchu", + ["You are hungry"] = "Jestes glodny", + ["You are paralysed"] = "Jestes sparalizowany", + ["You are poisoned"] = "Jestes zatruty", + ["You are protected by a magic shield"] = "Jestes chroniony magiczna tarcza", + ["You are strengthened"] = "Jestes wzmocniony", + ["You are within a protection zone"] = "Jestes w strefie ochronnej", + ["You can enter new text."] = "Mozesz wprowadzic nowy tekst.", + ["You have %s percent"] = "Masz %s procent", + ["You have %s percent to go"] = "Brakuje Ci %s procent", + ["You may not logout during a fight"] = "Nie mozesz sie wylogowac w trakcie walki", + ["You may not logout or enter a protection zone"] = "Nie mozesz sie wylogowac ani wejsc do strefy ochronnej", + ["You must enter a comment."] = "Prosze wprowadzic komentarz", + ["You must enter a valid server address and port."] = "Prosze wprowadzic poprawny adres i port.", + ["You must select a character to login!"] = "Musisz wybrac postac aby sie zalogowac!", + ["Your Capacity:"] = "Twoja Ladownosc:", + ["You read the following, written by \n%s\n"] = false, + ["You read the following, written on \n%s.\n"] = false, + ["Your Money:"] = "Twoje pieniadze", + ["Enable dash walking"] = "Wlacz szybsze chodzenie (dash walking)", + ["Will boost your walk on high speed characters"] = "Przyspieszy poruszanie sie postaci o wysokiej predkosci", + ["Display creature names"] = "Wyswietlaj nazwy potworow", + ["Display creature health bars"] = "Wyswietlaj paski zycia potworow", + ["Display text messages"] = "Wyswietlaj wiadomosci tekstowe", + ["Change language"] = "Zmien jezyk", + ["Enable lights"] = "Wlacz oswietlenie", + ["Enable audio"] = "Wlacz dzwiek", + ["Enable music sound"] = "Wlacz muzyke", + ["Music volume: %d"] = "Glosnosc muzyki: %d", + ["Audio"] = "Dzwiek", + ["Server List"] = "Lista serwerow", + ["Server list"] = "Lista serwerow", + ["Client Version"] = "Wersja klienta", + ["Add new server"] = "Dodaj nowy serwer", + ["Select"] = "Wybierz", + ["New Server"] = "Nowy serwer", + ["Host"] = false, + ["Reset All"] = "Ustaw domyslne", + ["Disable chat mode, allow to walk using ASDW"] = "Zablokuj tryb rozmow, zezwol na poruszanie sie za pomoca klawiszy ADSW", + ["Name"] = "Imie", + ["Price"] = "Cena", + ["Your Money"] = "Twoje fundusze", + ["Weight"] = "Waga", + ["Your Capacity"] = "Twoj udzwig", + ["Search"] = "Szukaj", + ["Sell All"] = "Sprzedaj wszystko", + ["Statement"] = "Stanowisko", + ["Reason"] = "Powod", + ["Action"] = "Akcja", + ["Comment"] = "Komentarz", + ["Balance"] = "Stan konta", + ["Offer Type"] = "Typ oferty", + ["Piece Price"] = "Cena jednego", + ["Find"] = "Szukaj", + ["Formula"] = "Formula", + ["Group"] = "Groupa", + ["Type"] = "Typ", + ["Cooldown"] = "Czas odnowienia", + ["Premium"] = false, + ["Any"] = "Dowolny", + ["Sorcerer"] = "Czarodziej", + ["Druid"] = false, + ["Paladin"] = "Paladyn", + ["Knight"] = "Rycerz" + } +} + +modules.client_locales.installLocale(locale) diff --git a/800OTClient/data/locales/pt.lua b/800OTClient/data/locales/pt.lua new file mode 100644 index 0000000..e5dc3b0 --- /dev/null +++ b/800OTClient/data/locales/pt.lua @@ -0,0 +1,413 @@ +locale = { + name = "pt", + charset = "cp1252", + languageName = "Português", + + formatNumbers = true, + decimalSeperator = ',', + thousandsSeperator = '.', + + -- As traduções devem vir sempre em ordem alfabética. + translation = { + ["%d of experience per hour"] = "%d de experiência por hora", + ["%s of experience left"] = "%s de experiência faltando", + ["%s: (use object on target)"] = "%s: (usar objeto no alvo)", + ["%s: (use object on yourself)"] = "%s: (usar objeto em si)", + ["%s: (use object with crosshair)"] = "%s: (usar objeto com mira)", + ["%s: (use object)"] = "%s: (usar objeto)", + ["1a) Offensive Name"] = "1a) Nome ofensivo", + ["1b) Invalid Name Format"] = "1b) Nome com formato inválido", + ["1c) Unsuitable Name"] = "1c) Nome não adequado", + ["1d) Name Inciting Rule Violation"] = "1d) Nome estimulando violação de regra", + ["2a) Offensive Statement"] = "2a) Afirmação ofensiva", + ["2b) Spamming"] = "2b) Spamming", + ["2c) Illegal Advertising"] = "2c) Anúncio ilegal", + ["2d) Off-Topic Public Statement"] = "2d) Afirmação pública fora de contexto", + ["2e) Non-English Public Statement"] = "2e) Afirmação pública em lingua não inglesa", + ["2f) Inciting Rule Violation"] = "2f) Estimulando violação de regra", + ["3a) Bug Abuse"] = "3a) Abuso de falhas", + ["3b) Game Weakness Abuse"] = "3b) Abuso de falhas no jogo", + ["3c) Using Unofficial Software to Play"] = "3c) Uso de programas ilegais para jogar", + ["3d) Hacking"] = "3d) Hacking", + ["3e) Multi-Clienting"] = "3e) Uso de mais de um cliente para jogar", + ["3f) Account Trading or Sharing"] = "3f) Troca de contas ou compartilhamento", + ["4a) Threatening Gamemaster"] = "4a) Ameaçar Gamemaster", + ["4b) Pretending to Have Influence on Rule Enforcement"] = "4b) Fingir ter influencia sobre a execução de regras", + ["4c) False Report to Gamemaster"] = "4c) Relatório falso para Gamemaster", + ["Accept"] = "Aceitar", + ["Account name"] = "Nome da conta", + ["Account Status"] = "Estado da Conta", + ["Action"] = "Ação", + ["Add new server"] = "Adicionar novo servidor", + ["Add new VIP"] = "Adicionar nova VIP", + ["Add to VIP list"] = "Adicionar a lista VIP", + ["Add"] = "Adicionar", + ["Addon 1"] = "Addon 1", + ["Addon 2"] = "Addon 2", + ["Addon 3"] = "Addon 3", + ["Adjust volume"] = "Ajustar volume", + ["Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nSimply click on Ok to resume your journeys!"] = false, + ["All modules and scripts were reloaded."] = "Todos módulos e scripts foram recarregados.", + ["All"] = "Todos", + ["Allow auto chase override"] = "Permitir sobrescrever o modo de perseguição", + ["Also known as dash in tibia community, recommended\nfor playing characters with high speed"] = "Também conhecido como dash na comunidade tibiana, recomendado\npara jogar com personagem que possuam velocidade alta", + ["Ambient light: %s%%"] = "Luz ambiente: %s%%", + ["Amount"] = "Quantidade", + ["Anonymous"] = "Anônimo", + ["Any"] = "Qualquer", + ["Are you sure you want to logout?"] = "Você tem certeza que quer sair?", + ["Attack"] = "Atacar", + ["Audio"] = "Áudio", + ["Author"] = "Autor", + ["Auto login selected character on next charlist load"] = "Entrar automaticamente com o personagem quando reabrir a lista de personagens", + ["Auto login"] = "Entrar automaticamente", + ["Autoload priority"] = "Prioridade de carregamento", + ["Autoload"] = "Carregar automaticamente", + ["Axe Fighting"] = "Combate com Machado", + ["Balance"] = "Saldo", + ["Banishment + Final Warning"] = "Banimento + Aviso final", + ["Banishment"] = "Banimento", + ["Battle"] = "Batalha", + ["Browse"] = "Navegar", + ["Bug report sent."] = "Reporte de bug enviado.", + ["Button Assign"] = "Selecionar botão", + ["Buy Now"] = "Comparar agora", + ["Buy Offers"] = "Ofertas de compra", + ["Buy with backpack"] = "Comprar com mochila", + ["Buy"] = "Comprar", + ["Cancel"] = "Cancelar", + ["Cannot login while already in game."] = "Não é possivel logar enquanto já estiver jogando.", + ["Cap"] = "Cap", + ["Capacity"] = "Capacidade", + ["Center"] = "Centro", + ["Change language"] = "Trocar língua", + ["Channels"] = "Canais", + ["Character List"] = "Lista de personagens", + ["Classic control"] = "Controle clássico", + ["Clear current message window"] = "Apagar mensagens", + ["Clear Messages"] = "Limpar mensagens", + ["Clear object"] = "Limpar objeto", + ["Client needs update."] = "O client do jogo precisa ser atualizado", + ["Close this channel"] = "Fechar esse canal", + ["Close"] = "Fechar", + ["Club Fighting"] = "Combate com Porrete", + ["Combat Controls"] = "Controles de combate", + ["Comment"] = "Comentário", + ["Connecting to game server..."] = "Conectando no servidor do jogo...", + ["Connecting to login server..."] = "Conectando no servidor de autenticação...", + ["Connection Error"] = "Erro de Conexão", + ["Console"] = "Console", + ["Cooldown"] = "Cooldown", + ["Cooldowns"] = "Cooldowns", + ["Copy message"] = "Copiar mensagem", + ["Copy name"] = "Copiar nome", + ["Copy Name"] = "Copiar Nome", + ["Create Map Mark"] = "Criar marca no mapa", + ["Create mark"] = "Criar marca", + ["Create New Offer"] = "Criar nova oferta", + ["Create Offer"] = "Criar oferta", + ["Current hotkey to add: %s"] = "Atalho atual para adicionar: %s", + ["Current hotkeys:"] = "Atalhos atuais", + ["Current Offers"] = "Ofertas atuais", + ["Default"] = "Padrão", + ["Delete mark"] = "Deletar marca", + ["Description"] = "Descrição", + ["Description:"] = "Descrição", + ["Destructive Behaviour"] = "Comportamento destrutivo", + ["Detail"] = "Detalhe", + ["Details"] = "Detalhes", + ["Disable Shared Experience"] = "Desativar experiência compartilhada", + ["Dismount"] = "Desmontar", + ["Display connection speed to the server (milliseconds)"] = "Exibir a velocidade de conexão com o servidor (milisegundos)", + ["Display creature health bars"] = "Exibir barras de vida das criaturas", + ["Display creature names"] = "Exibir nomes das criaturas", + ["Display text messages"] = "Exibir mensagens de texto", + ["Distance Fighting"] = "Combate a Distância", + ["Don't stretch or shrink Game Window"] = "Não esticar ou contrair a janela do game", + ["Druid"] = "Druid", + ["Edit hotkey text:"] = "Editar texto do atalho", + ["Edit List"] = "Editar lista", + ["Edit Text"] = "Editar Texto", + ["Enable audio"] = "Ativar áudio", + ["Enable dash walking"] = "Ativar andar rápido", + ["Enable lights"] = "Ativar luzes", + ["Enable music sound"] = "Ativar música", + ["Enable music"] = "Ativar musica", + ["Enable shader effects"] = "Ativar efeitos shader", + ["Enable Shared Experience"] = "Ativar experiência compartilhada", + ["Enable smart walking"] = "Ativar andar inteligente", + ["Enable vertical synchronization"] = "Ativar sincronização vertical", + ["Enable walk booster"] = "Ativar andar intensificado", + ["Enter Game"] = "Entrar no jogo", + ["Enter one name per line."] = "Entre somente um nome por linha.", + ["Enter with your account again to update your client."] = "Entre com sua conta denovo para atualizar o client.", + ["Error"] = "Erro", + ["Excessive Unjustified Player Killing"] = "Assassinato em excesso, sem justificativa, de jogadores", + ["Exclude from private chat"] = "Excluir do canal privado", + ["Exit"] = "Sair", + ["Experience"] = "Experiência", + ["Filter list to match your level"] = "Filtrar a lista para o seu level", + ["Filter list to match your vocation"] = "Filtrar a lista para a sua vocação", + ["Find"] = "Procurar", + ["Fishing"] = "Pesca", + ["Fist Fighting"] = "Porrada", + ["Follow"] = "Seguir", + ["For Your Information"] = "Para sua informação", + ["Force Exit"] = "Forçar Saida", + ["Formula"] = "Fórmula", + ["Free Account"] = "Conta Grátis", + ["Fullscreen"] = "Tela cheia", + ["Game framerate limit: %s"] = "Limite da taxa de quadros do jogo: %s", + ["Game"] = "Jogo", + ["Graphics card driver not detected"] = "Driver da placa de vídeo não detectado", + ["Graphics Engine:"] = "Motor Gráfico:", + ["Graphics"] = "Gráficos", + ["Group"] = "Grupo", + ["Head"] = "Cabeça", + ["Healing"] = "Curando", + ["Health Info"] = "Barra de Vida", + ["Health Information"] = "Informação de vida", + ["Hide monsters"] = "Esconder montros", + ["Hide non-skull players"] = "Esconder jogadores sem caveira", + ["Hide Npcs"] = "Esconder NPCs", + ["Hide Offline"] = "Esconder Offline", + ["Hide party members"] = "Esconder membros do grupo", + ["Hide players"] = "Esconder jogadores", + ["Hide spells for higher exp. levels"] = "Esconder feitiços de nível maior", + ["Hide spells for other vocations"] = "Esconder feitiços de outras vocações", + ["Hit Points"] = "Pontos de Vida", + ["Hold left mouse button to navigate\nScroll mouse middle button to zoom\nRight mouse button to create map marks"] = "Segure o botão esquerdo para navegar\nGire o botão do centro do mouse para ampliar\nClique com o botão direito do mouse para criar marcas", + ["Host"] = "Host", + ["Hotkeys"] = "Atalhos", + ["If you shut down the program, your character might stay in the game.\nClick on 'Logout' to ensure that you character leaves the game properly.\nClick on 'Exit' if you want to exit the program without logging out your character."] = "Se você desligar o programa o seu personagem pode continuar no jogo.\nClique em 'Sair' para assegurar que seu personagem saia do jogo adequadamente.\nClique em 'Forçar Saida' para fechar o programa sem desconectar seu personagem.", + ["Ignore capacity"] = "Ignorar capacidade", + ["Ignore equipped"] = "Ignorar equipado", + ["Ignore List"] = "Lista de Ignorados", + ["Ignore players"] = "Jogadores ignorados", + ["Ignore Private Messages"] = "Ignorar mensagens privadas", + ["Ignore Yelling"] = "Ignorar gritos", + ["Ignore"] = "Ignorar", + ["Ignored players:"] = "Joadores ignorados:", + ["Interface framerate limit: %s"] = "Limite da taxa de quadros da interface: %s", + ["Inventory"] = "Inventário", + ["Invite to Party"] = "Convidar para o grupo", + ["Invite to private chat"] = "Convidar para o canal privado", + ["IP Address Banishment"] = "Banimento de endereço IP", + ["It is empty."] = "Está vazio.", + ["Item Offers"] = "Ofertas de items", + ["Join %s's Party"] = "Entrar na party do %s", + ["Knight"] = "Knight", + ["Leave Party"] = "Sair do grupo", + ["Level"] = "Nível", + ["Lifetime Premium Account"] = "Conta Premium para a vida toda.", + ["Limits FPS to 60"] = "Limita o FPS para 60", + ["List of items that you're able to buy"] = "Lista de itens que você pode comprar", + ["List of items that you're able to sell"] = "Lista de itens que você pode vender", + ["Load"] = "Carregar", + ["Logging out..."] = "Saindo...", + ["Login Error"] = "Erro de Autenticação", + ["Login"] = "Entrar", + ["Logout"] = "Sair", + ["Look"] = "Olhar", + ["Magic Level"] = "Nível Mágico", + ["Make sure that your client uses\nthe correct game protocol version"] = "Tenha certeza que o seu cliente use\no mesmo protocolo do servidor do jogo", + ["Mana"] = "Mana", + ["Manage hotkeys:"] = "Configurar atalhos:", + ["Market Offers"] = "Ofertas do mercado", + ["Market"] = "Mercado", + ["Message of the day"] = "Mensagem do dia", + ["Message to "] = "Mensagem para ", + ["Message to %s"] = "Mandar mensagem para %s", + ["Minimap"] = "Minimapa", + ["Module Manager"] = "Gerenciador de Módulos", + ["Module name"] = "Nome do módulo", + ["Mount"] = "Montar", + ["Move Stackable Item"] = "Mover item contável", + ["Move up"] = "Mover para cima", + ["Music volume: %d"] = "Volume da música: %d", + ["My Offers"] = "Minhas ofertas", + ["Name Report + Banishment + Final Warning"] = "Reportar Nome + Banimento + Aviso Final", + ["Name Report + Banishment"] = "Reportar Nome + Banimento", + ["Name Report"] = "Reportar Nome", + ["Name"] = "Nome", + ["New Server"] = "Novo Servidor", + ["Next level in %d hours and %d minutes"] = "Próximo nível em %d horas e %d minutos", + ["No graphics card detected, everything will be drawn using the CPU,\nthus the performance will be really bad.\nPlease update your graphics driver to have a better performance."] = false, + ["No item selected."] = "Nenhum item selecionado", + ["No Mount"] = "Sem montaria", + ["No Outfit"] = "Sem roupa", + ["No statement has been selected."] = "Nenhuma afirmação foi selecionada.", + ["No"] = "Não", + ["Notation"] = "Notação", + ["NPC Trade"] = "Troca com NPC", + ["Offer History"] = "Histórico de ofertas", + ["Offer Type"] = "Tipo de oferta", + ["Offers"] = "Ofertas", + ["Offline Training"] = "Treino Offline", + ["Ok"] = "Ok", + ["on %s.\n"] = "em %s.\n", + ["Open a private message channel:"] = "Abrir um canal privado:", + ["Open charlist automatically when starting client"] = "Abrir lista de personagens ao iniciar o cliente", + ["Open in new window"] = "Abrir em nova janela", + ["Open new channel"] = "Abrir novo canal", + ["Open"] = "Abrir", + ["Options"] = "Opções", + ["Overview"] = "Visão geral", + ["Paladin"] = "Paladin", + ["Pass Leadership to %s"] = "Passar liderança para %s", + ["Password"] = "Senha", + ["Piece Price"] = "Preço por peça", + ["Please enter a character name:"] = "Por favor, entre com o nome do personagem:", + ["Please Select"] = "Por favor, selecione algo", + ["Please use this dialog to only report bugs. Do not report rule violations here!"] = "Por favor, use este campo apenas para reportar defeitos. Não reporte violação de regras aqui!", + ["Please wait"] = "Por favor, espere", + ["Please, press the key you wish to add onto your hotkeys manager"] = "Por favor, pressione a tecla que você deseja\nadicionar no gerenciador de atalhos", + ["Port"] = "Porta", + ["Position"] = "Posição", + ["Position: %i %i %i"] = "Posição: %i %i %i", + ["Premium Account (%s) days left"] = "Conta Premium (%s) dias faltando", + ["Premium"] = "Premium", + ["Price"] = "Preço", + ["Primary"] = "Primário", + ["Protocol"] = "Protocolo", + ["Quest Log"] = "Registro de Quest", + ["Randomize characters outfit"] = "Gerar roupa aleatória", + ["Randomize"] = "Embaralhar", + ["Reason"] = "Motivo", + ["Refresh Offers"] = "Atualizar Ofertas", + ["Refresh"] = "Atualizar", + ["Regeneration Time"] = "Tempo de Regeneração", + ["Reject"] = "Rejeitar", + ["Reload All"] = "Recarregar Todos", + ["Remember account and password when starts client"] = "Lembrar conta e senha quando iniciar o cliente", + ["Remember password"] = "Lembrar senha", + ["Remove %s"] = "Remover %s", + ["Remove"] = "Remover", + ["Report Bug"] = "Reportar defeito", + ["Reserved for more functionality later."] = "Reservado para futura maior funcionalidade.", + ["Reset All"] = "Resetar Todos", + ["Reset Market"] = "Resetar Mercado", + ["Revoke %s's Invitation"] = "Não aceitar o convite do %s", + ["Rotate"] = "Girar", + ["Rule Violation"] = "Violação de regra", + ["Save Messages"] = "Salvar Mensagens", + ["Save"] = "Salvar", + ["Search all items"] = "Procurar todos os items", + ["Search"] = "Procurar", + ["Secondary"] = "Secundário", + ["Select object"] = "Selecionar objeto", + ["Select Outfit"] = "Selecionar Roupa", + ["Select your language"] = "Selecione sua língua", + ["Select"] = "Selecionar", + ["Sell All"] = "Vender Todos", + ["Sell Now"] = "Vender agora", + ["Sell Offers"] = "Ofertas de venda", + ["Sell"] = "Vender", + ["Send automatically"] = "Enviar automaticamente", + ["Send Message"] = "Enviar Mensagem", + ["Send"] = "Enviar", + ["Server List"] = "Lista de Servidores", + ["Server list"] = "Lista de servidores", + ["Server Log"] = "Registro do servidor", + ["Server"] = "Servidor", + ["Set Outfit"] = "Escolher Roupa", + ["Shielding"] = "Defesa", + ["Show all items"] = "Exibir todos os itens", + ["Show connection ping"] = "Mostrar latência de conexão", + ["Show Depot Only"] = "Mostrar somente o depósito", + ["Show event messages in console"] = "Exibir mensagens de eventos no console", + ["Show frame rate"] = "Exibir FPS", + ["Show info messages in console"] = "Exibir mensagens informativas no console", + ["Show left panel"] = "Mostrar barra lateral esquerda", + ["Show levels in console"] = "Exibir níveis no console", + ["Show Offline"] = "Mostrar Offline", + ["Show private messages in console"] = "Exibir mensagens privadas no console", + ["Show private messages on screen"] = "Exibir mensagens na tela", + ["Show Server Messages"] = "Mostrar Mensagens do Servidor", + ["Show status messages in console"] = "Exibir mensagens de estado no console", + ["Show Text"] = "Mostrar texto", + ["Show timestamps in console"] = "Exibir o horário no console", + ["Show your depot items only"] = "Mostrar os itens somentedo depósito", + ["Skills"] = "Habilidades", + ["Sorcerer"] = "Sorcerer", + ["Soul Points"] = "Pontos de Alma", + ["Soul"] = "Alma", + ["Special"] = "Especial", + ["Speed"] = "Velocidade", + ["Spell Cooldowns"] = "", + ["Spell List"] = "Lista de Feitiços", + ["Stamina"] = "Vigor", + ["Statement Report"] = "Afirmar Relato", + ["Statement"] = "Afirmação", + ["Statistics"] = "Estatísticas", + ["Stop Attack"] = "Parar de Atacar", + ["Stop Follow"] = "Parar de Seguir", + ["Support"] = "Suporte", + ["Sword Fighting"] = "Combate com Espada", + ["Terminal"] = "Terminal", + ["There is no way."] = "Não há rota", + ["Title"] = "Título", + ["Total Price"] = "Preço total", + ["Trade with ..."] = "Trocar com ...", + ["Trade"] = "Trocar", + ["Trying to reconnect in %s seconds."] = "Tentando reconectar em %s segundos.", + ["Type"] = "Tipo", + ["Unable to establish a connection. (err: %d)"] = "Não foi possível estabilizar a conexã. (err: %d)", + ["Unable to load dat file, please place a valid dat in '%s'"] = "Não foi possível carregar o arquivo DAT, por favor coloque um arquivo válido em %s", + ["Unable to load spr file, please place a valid spr in '%s'"] = "Não foi possível carregar o arquivo SPR, por favor coloque um arquivo válido em %s", + ["Unable to logout."] = "Não é possivel sair", + ["Unignore"] = "Designorar", + ["Unload"] = "Descarregar", + ["Update needed"] = "Atualização necessária", + ["Use on target"] = "Usar no alvo", + ["Use on yourself"] = "Usar em si", + ["Use with ..."] = "Usar com ...", + ["Use"] = "Usar", + ["Version"] = "Versão", + ["VIP List"] = "Lista VIP", + ["Voc."] = "Voc.", + ["Vocation"] = "Vocação", + ["Waiting List"] = "Lista de espera", + ["Website"] = "Website", + ["Weight"] = "Peso", + ["Will boost your walk on high speed characters"] = "Irá melhorar o andar de persnagens rápidos", + ["Will detect when to use diagonal step based on the\nkeys you are pressing"] = "Detectar quando usar o passo diagonal\nbaseado nas teclas pressionadas", + ["With crosshair"] = "Com mira", + ["Yes"] = "Sim", + ["You are bleeding"] = "Você está sangrando", + ["You are burning"] = "Você está queimando", + ["You are cursed"] = "Você está amaldiçoado", + ["You are dazzled"] = "Você está deslumbrado", + ["You are dead"] = "Você está morto", + ["You are dead."] = "Você está morto.", + ["You are drowning"] = "Você está se afogando", + ["You are drunk"] = "Você está bêbado", + ["You are electrified"] = "Você está eletrificado", + ["You are freezing"] = "Você está congelando", + ["You are hasted"] = "Você está com pressa", + ["You are hungry"] = "Você está faminto", + ["You are paralysed"] = "Você está paralizado", + ["You are poisoned"] = "Você está envenenado", + ["You are protected by a magic shield"] = "Você está protegido com um escudo mágico", + ["You are strengthened"] = "Você está reforçado", + ["You are within a protection zone"] = "Você está dentro de uma zona de proteção", + ["You can enter new text."] = "Você pode entrar com um novo texto.", + ["You have %d%% to advance to level %d."] = "Você tem %d%% para avançar para o nível %d.", + ["You have %s percent to go"] = "Você tem %s porcento para avançar", + ["You have %s percent"] = "Você tem %s porcento", + ["You may not logout during a fight"] = "Você não pode sair durante um combate", + ["You may not logout or enter a protection zone"] = "Você não pode sair ou entrar em uma zona de proteção", + ["You must enter a comment."] = "Você precisa entrar com um comentário", + ["You must enter a valid server address and port."] = "Você precisa colocar um endereço e uma porta do servidor válidos.", + ["You must select a character to login!"] = "Você deve selecionar um personagem para entrar!", + ["You read the following, written by \n%s\n"] = "Você lê o seguinte, escrito por \n%s\n", + ["You read the following, written on \n%s.\n"] = "Você lê o seguinte, escrito em \n%s.\n", + ["Your Capacity"] = "Sua capacidade", + ["Your character health is %d out of %d."] = "A vida do seu personagem é %d de %d.", + ["Your character mana is %d out of %d."] = "A mana do seu personagem é %d de %d.", + ["Your connection has been lost. (err: %d)"] = "A sua conexão foi perdida. (err: %d)", + ["Your Money"] = "Seu dinheiro", + } +} + +modules.client_locales.installLocale(locale) diff --git a/800OTClient/data/locales/sv.lua b/800OTClient/data/locales/sv.lua new file mode 100644 index 0000000..d6263f4 --- /dev/null +++ b/800OTClient/data/locales/sv.lua @@ -0,0 +1,378 @@ +-- thanks cometangel, who made these translations + +locale = { + name = "sv", + charset = "cp1252", + languageName = "Svenska", + + formatNumbers = true, + decimalSeperator = ',', + thousandsSeperator = ' ', + + translation = { + ["1a) Offensive Name"] = "1a) Offensivt Namn", + ["1b) Invalid Name Format"] = "1b) Ogiltigt Namnformat", + ["1c) Unsuitable Name"] = "1c) Opassande Namn", + ["1d) Name Inciting Rule Violation"] = "1d) Namn anstiftar regelbrott.", + ["2a) Offensive Statement"] = "2a) Offensivt Uttryck", + ["2b) Spamming"] = "2b) Spammning", + ["2c) Illegal Advertising"] = "2c) Olaglig Reklamföring", + ["2d) Off-Topic Public Statement"] = "2d) Icke-Ämneförhållande publiskt uttryck", + ["2e) Non-English Public Statement"] = "2e) Icke-Engelskt publiskt uttryck", + ["2f) Inciting Rule Violation"] = "2f) Antyder regelbrytande", + ["3a) Bug Abuse"] = "3a) Missbrukande av bugg", + ["3b) Game Weakness Abuse"] = "3b) Spelsvaghetsmissbruk", + ["3c) Using Unofficial Software to Play"] = "3c) Använder Icke-officiel mjukvara för att spela", + ["3d) Hacking"] = "3d) Hackar", + ["3e) Multi-Clienting"] = "3e) Multi-klient", + ["3f) Account Trading or Sharing"] = "3f) Kontohandel", + ["4a) Threatening Gamemaster"] = "4a) Hotar gamemaster", + ["4b) Pretending to Have Influence on Rule Enforcement"] = "4b) Låtsas ha inflytande på Regelsystem", + ["4c) False Report to Gamemaster"] = "4c) Falsk rapport till gamemaster", + ["Accept"] = "Acceptera", + ["Account name"] = "Konto namn", + ["Account Status:"] = false, + ["Action:"] = "Handling:", + ["Add"] = "Lägg till", + ["Add new VIP"] = "Ny VIP", + ["Addon 1"] = "Tillägg 1", + ["Addon 2"] = "Tillägg 2", + ["Addon 3"] = "Tillägg 3", + ["Add to VIP list"] = "Lägg till på VIP Listan", + ["Adjust volume"] = "Justera Volym", + ["Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nSimply click on Ok to resume your journeys!"] = false, + ["All"] = false, + ["All modules and scripts were reloaded."] = "Alla moduler och skript laddades om", + ["Allow auto chase override"] = "Tillåt Jaktstyrning", + ["Also known as dash in tibia community, recommended\nfor playing characters with high speed"] = false, + ["Ambient light: %s%%"] = false, + ["Amount:"] = "Antal:", + ["Amount"] = "Antal", + ["Anonymous"] = "Anonym", + ["Are you sure you want to logout?"] = "Är du säker att du vill logga ut?", + ["Attack"] = "Attackera", + ["Author"] = "Översättare", + ["Autoload"] = "Automatisk Laddning", + ["Autoload priority"] = "Laddningsprioritet", + ["Auto login"] = "Autoinloggning", + ["Auto login selected character on next charlist load"] = "Logga in Näst laddad karaktär automatisk nästa gång karaktärlistan laddar", + ["Axe Fighting"] = "Yx Stridande", + ["Balance:"] = "Balans:", + ["Banishment"] = "Bannlysning", + ["Banishment + Final Warning"] = "Bannlysning + Sista varning", + ["Battle"] = "Strid", + ["Browse"] = "Bläddra", + ["Bug report sent."] = "Buggrapport Skickad.", + ["Button Assign"] = "Assignera Knapp", + ["Buy"] = "Köp", + ["Buy Now"] = "Köp Nu", + ["Buy Offers"] = "Köp Offerter", + ["Buy with backpack"] = "Köp med ryggsäck", + ["Cancel"] = "Avbryt", + ["Cannot login while already in game."] = "Kan ej logga in medan du redan är i spelet.", + ["Cap"] = "Kap", + ["Capacity"] = "Kapacitet", + ["Center"] = "Centrera", + ["Channels"] = "Kanaler", + ["Character List"] = "Karaktär lista", + ["Classic control"] = "Klassisk kontroll", + ["Clear current message window"] = "Rensa nuvarande meddelanderuta", + ["Clear Messages"] = false, + ["Clear object"] = "Rensa objekt", + ["Client needs update."] = "Klienten behöver uppdateras.", + ["Close"] = "Stäng", + ["Close this channel"] = "Stäng Denna Kanal", + ["Club Fighting"] = "Klubb Stridande", + ["Combat Controls"] = "Krigs Kontroller", + ["Comment:"] = "Kommentar:", + ["Connecting to game server..."] = "Kopplar upp till spelserver...", + ["Connecting to login server..."] = "Kopplar upp till autentiseringserver...", + ["Console"] = false, + ["Cooldowns"] = false, + ["Copy message"] = "Kopiera meddelande", + ["Copy name"] = "Kopiera namn", + ["Copy Name"] = "Kopiera Namn", + ["Create Map Mark"] = false, + ["Create mark"] = false, + ["Create New Offer"] = "Skapa ny offert.", + ["Create Offer"] = "Skapa Offert", + ["Current hotkeys:"] = "Aktuella snabbtangenter", + ["Current hotkey to add: %s"] = "Ny Snabbtangent: %s", + ["Current Offers"] = "Nuvarande Offerter", + ["Default"] = "Standard", + ["Delete mark"] = false, + ["Description:"] = false, + ["Description"] = "Beskrivning", + ["Destructive Behaviour"] = "Destruktivt beteende", + ["Detail"] = "Detalj", + ["Details"] = "Detaljer", + ["Disable Shared Experience"] = "Avaktivera delad erfarenhet", + ["Dismount"] = false, + ["Display connection speed to the server (milliseconds)"] = false, + ["Distance Fighting"] = "Distans Stridande", + ["Don't stretch/shrink Game Window"] = false, + ["Edit hotkey text:"] = "Ändra Snabbtangent:", + ["Edit List"] = "Ändra Lista", + ["Edit Text"] = "Ändra text", + ["Enable music"] = "Aktivera musik", + ["Enable Shared Experience"] = "Aktivera delad erfarenhet", + ["Enable smart walking"] = false, + ["Enable vertical synchronization"] = "Aktivera vertikal synkronisering", + ["Enable walk booster"] = false, + ["Enter Game"] = "Gå in i Spelet", + ["Enter one name per line."] = "Skriv ett namn per linje.", + ["Enter with your account again to update your client."] = false, + ["Error"] = "Fel", + ["Error"] = "Fel", + ["Excessive Unjustified Player Killing"] = "Överdrivet oberättigat dödande av spelare", + ["Exclude from private chat"] = "Exkludera från privat chat", + ["Exit"] = "Avsluta", + ["Experience"] = "Erfarenhet", + ["Filter list to match your level"] = "Filtrera efter nivå", + ["Filter list to match your vocation"] = "Filtrera efter kallelse", + ["Find:"] = false, + ["Fishing"] = "Fiske", + ["Fist Fighting"] = "Hand Stridande", + ["Follow"] = "Följ", + ["Force Exit"] = false, + ["For Your Information"] = false, + ["Free Account"] = false, + ["Fullscreen"] = "Helskärm", + ["Game"] = false, + ["Game framerate limit: %s"] = "Spelets FPS gräns: %s", + ["Graphics"] = "Grafik", + ["Graphics card driver not detected"] = false, + ["Graphics Engine:"] = "Grafikmotor:", + ["Head"] = "Huvud", + ["Healing"] = false, + ["Health Info"] = "Livsinfo", + ["Health Information"] = "Livsinformation", + ["Hide monsters"] = "Göm Monster", + ["Hide non-skull players"] = "Göm icke-skullad spelare", + ["Hide Npcs"] = "Göm NPCs", + ["Hide Offline"] = false, + ["Hide party members"] = "Göm gruppmedlemmar", + ["Hide players"] = "Göm spelare", + ["Hide spells for higher exp. levels"] = false, + ["Hide spells for other vocations"] = false, + ["Hit Points"] = "Livspoäng", + ["Hold left mouse button to navigate\nScroll mouse middle button to zoom\nRight mouse button to create map marks"] = false, + ["Hotkeys"] = "Snabbtangenter", + ["If you shut down the program, your character might stay in the game.\nClick on 'Logout' to ensure that you character leaves the game properly.\nClick on 'Exit' if you want to exit the program without logging out your character."] = "Om du stänger av programmet kan din karaktär stanna i spelet.\nKlicka på 'Logga ut' för att säkerställa att din karaktär lämnar spelet korrekt.\nKlicka på 'Avsluta' om du vill avsluta programmet utan att logga ut din karaktär.", + ["Ignore"] = false, + ["Ignore capacity"] = "Ignorera kapacitet", + ["Ignored players:"] = false, + ["Ignore equipped"] = "Ignorera utrustning", + ["Ignore List"] = false, + ["Ignore players"] = false, + ["Ignore Private Messages"] = false, + ["Ignore Yelling"] = false, + ["Interface framerate limit: %s"] = "Gränssnitt FPS gräns: %s", + ["Inventory"] = "Utrustning", + ["Invite to Party"] = "Bjud till grupp", + ["Invite to private chat"] = "Bjud in i privat chat", + ["IP Address Banishment"] = "Bannlysning av IP", + ["Item Offers"] = "Objekt offert", + ["It is empty."] = false, + ["Join %s's Party"] = "Gå med i %s's Grupp", + ["Leave Party"] = "Lämna Grupp", + ["Level"] = "Nivå", + ["Lifetime Premium Account"] = false, + ["Limits FPS to 60"] = "Stoppa FPS vid 60", + ["List of items that you're able to buy"] = "Lista av saker du kan köpa", + ["List of items that you're able to sell"] = "Lista av saker du kan sälja", + ["Load"] = "Ladda", + ["Logging out..."] = "Loggar ut...", + ["Login"] = "Logga in", + ["Login Error"] = "Autentifikations fel", + ["Login Error"] = "Autentifikations fel", + ["Logout"] = "Logga ut", + ["Look"] = "Kolla", + ["Magic Level"] = "Magisk Nivå", + ["Make sure that your client uses\nthe correct game protocol version"] = "Var säker på att din client\n andvänder rätt protokol version", + ["Mana"] = "Mana", + ["Manage hotkeys:"] = "Ändra snabbtangenten:", + ["Market"] = "Marknad", + ["Market Offers"] = "Marknadsofferter", + ["Message of the day"] = "Dagens meddelande", + ["Message to "] = "Meddelande till ", + ["Message to %s"] = "Meddelande till %s", + ["Minimap"] = "Minikarta", + ["Module Manager"] = "Modul Manager", + ["Module name"] = "Modul namn", + ["Mount"] = false, + ["Move Stackable Item"] = "Flytta stapelbart föremål", + ["Move up"] = "Flytta upp", + ["My Offers"] = "Mina offerter", + ["Name:"] = "Namn:", + ["Name Report"] = "Namn Rapport", + ["Name Report + Banishment"] = "Namn rapport + Bannlysning", + ["Name Report + Banishment + Final Warning"] = "Namn rapport + Bannlysning + Sista varning", + ["No"] = "Nej", + ["No graphics card detected, everything will be drawn using the CPU,\nthus the performance will be really bad.\nPlease update your graphics driver to have a better performance."] = false, + ["No item selected."] = "Ingen sak vald.", + ["No Mount"] = "Ingen Mount", + ["No Outfit"] = "Ingen Utstyrsel", + ["No statement has been selected."] = "Inget påstående är valt.", + ["Notation"] = "Notering", + ["NPC Trade"] = "Handel NPC", + ["Offer History"] = "Offert Historia", + ["Offers"] = "Offerter", + ["Offer Type:"] = "Offert typ:", + ["Offline Training"] = false, + ["Ok"] = "Ok", + ["on %s.\n"] = "på %s.\n", + ["Open"] = "Öppna", + ["Open a private message channel:"] = "Öppna en privat meddelandekanal:", + ["Open charlist automatically when starting client"] = false, + ["Open in new window"] = "Öppna i nytt fönster", + ["Open new channel"] = "Öppna ny kanal", + ["Options"] = "Inställningar", + ["Overview"] = false, + ["Pass Leadership to %s"] = "Ge ledarskap till %s", + ["Password"] = "Lösenord", + ["Piece Price:"] = "Per Styck:", + ["Please enter a character name:"] = "Skriv in ett karaktärsnamn:", + ["Please, press the key you wish to add onto your hotkeys manager"] = "Tryck på knappen som du\nvill lägga till som snabbtangent", + ["Please Select"] = "Välj", + ["Please use this dialog to only report bugs. Do not report rule violations here!"] = "Använd den här dialogrutan endast för att rapportera buggar. Rapportera inte regelbrott här!", + ["Please wait"] = "Var God Vänta", + ["Port"] = "Port", + ["Position:"] = false, + ["Position: %i %i %i"] = false, + ["Premium Account (%s) days left"] = false, + ["Price:"] = "Pris", + ["Primary"] = "Primär", + ["Protocol"] = "Protokoll", + ["Quest Log"] = "Uppdragslog", + ["Randomize"] = "Slumpa", + ["Randomize characters outfit"] = "Slumpa karaktärs utstyrsel", + ["Reason:"] = "Anledning:", + ["Refresh"] = "Uppdatera", + ["Refresh Offers"] = false, + ["Regeneration Time"] = false, + ["Reject"] = "Avvisa", + ["Reload All"] = "Ladda om allt", + ["Remember account and password when starts client"] = false, + ["Remember password"] = "Kom ihåg lösenord", + ["Remove"] = "Ta bort", + ["Remove %s"] = "Ta bort %s", + ["Report Bug"] = "Rapportera Bugg", + ["Reserved for more functionality later."] = false, + ["Reset Market"] = false, + ["Revoke %s's Invitation"] = "Annulera %s's Inbjudan", + ["Rotate"] = "Rotera", + ["Rule Violation"] = "Regel Brott", + ["Save"] = false, + ["Save Messages"] = false, + ["Search:"] = "Sök:", + ["Search all items"] = false, + ["Secondary"] = "Sekundär", + ["Select object"] = "Välj Objekt", + ["Select Outfit"] = "Välj Utstyrsel", + ["Select your language"] = false, + ["Sell"] = "Sälj", + ["Sell Now"] = "Sälj Nu", + ["Sell Offers"] = "Sälj Offerter", + ["Send"] = "Skicka", + ["Send automatically"] = "Skicka automatiskt", + ["Send Message"] = false, + ["Server"] = "Server", + ["Server Log"] = "Server Log", + ["Set Outfit"] = "Bestäm Utstyrsel", + ["Shielding"] = "Sköld", + ["Show all items"] = "Visa alla saker", + ["Show connection ping"] = false, + ["Show Depot Only"] = "Visa bara förråd", + ["Show event messages in console"] = "Visa event meddelanden i konsol", + ["Show frame rate"] = "Visa FPS", + ["Show info messages in console"] = "Visa info meddelanden i konsol", + ["Show left panel"] = "Visa vänster panel", + ["Show levels in console"] = "Visa nivåer i konsol", + ["Show Offline"] = false, + ["Show private messages in console"] = "Visa privata meddelanden i konsol", + ["Show private messages on screen"] = "Visa privata meddelanden på skärmen", + ["Show Server Messages"] = false, + ["Show status messages in console"] = "Visa statusmeddelanden i konsol", + ["Show Text"] = "Visa Text", + ["Show timestamps in console"] = "Visa tidstämpel i konsol", + ["Show your depot items only"] = "Visa mitt förråd endast", + ["Skills"] = "Förmågor", + ["Soul"] = "Själ", + ["Soul Points"] = "Själpoäng", + ["Special"] = false, + ["Speed"] = false, + ["Spell Cooldowns"] = false, + ["Spell List"] = false, + ["Stamina"] = "Uthållighet", + ["Statement:"] = "Påstående:", + ["Statement Report"] = "Påståenderapport", + ["Statistics"] = "Statistik", + ["Stop Attack"] = "Sluta Attackera", + ["Stop Follow"] = "Sluta Följa", + ["Support"] = false, + ["%s: (use object)"] = "%s: (Använd objekt)", + ["%s: (use object on target)"] = "%s: (Använd objekt på mål)", + ["%s: (use object on yourself)"] = "%s: (Använd objekt på mig)", + ["%s: (use object with crosshair)"] = "%s: (Använd objekt med sikte)", + ["Sword Fighting"] = "Svärd Stridning", + ["Terminal"] = "Terminal", + ["There is no way."] = "Det finns ingen väg.", + ["Title"] = false, + ["Total Price:"] = "Totalt Pris:", + ["Trade"] = "Handel", + ["Trade with ..."] = "Handla med ...", + ["Trying to reconnect in %s seconds."] = "Försöker koppla upp igen om %s sekunder.", + ["Unable to load dat file, please place a valid dat in '%s'"] = "kan ej ladda dat filen, lägg en giltig dat fil i '%s'", + ["Unable to load spr file, please place a valid spr in '%s'"] = "kan ej ladda spr filen, lägg en giltig spr fil i '%s'", + ["Unable to logout."] = "Kan ej logga ut.", + ["Unignore"] = false, + ["Unload"] = "Avladda", + ["Update needed"] = false, + ["Use"] = "Använd", + ["Use on target"] = "Använd på mål", + ["Use on yourself"] = "Använd på mig", + ["Use with ..."] = "Använd med ...", + ["Version"] = "Version", + ["VIP List"] = "VIP Lista", + ["Voc."] = "Kallelse", + ["Vocation"] = false, + ["Waiting List"] = "Kölista", + ["Website"] = "Websida", + ["Weight:"] = "Vikt:", + ["Will detect when to use diagonal step based on the\nkeys you are pressing"] = false, + ["With crosshair"] = "Med sikte", + ["Yes"] = "Ja", + ["You are bleeding"] = "Du Blöder", + ["You are burning"] = "Du brinner", + ["You are cursed"] = "Du är fördömd", + ["You are dazzled"] = "Du är chockad", + ["You are dead."] = "Du är död.", + ["You are dead"] = "Du är död", + ["You are drowning"] = "Du drunknar", + ["You are drunk"] = "Du är full.", + ["You are electrified"] = "Du är elektrifierad", + ["You are freezing"] = "Du Fryser", + ["You are hasted"] = "Du är i hast", + ["You are hungry"] = "Du är hungrig", + ["You are paralysed"] = "Du är paralyserad", + ["You are poisoned"] = "Du är förgiftad", + ["You are protected by a magic shield"] = "Du är skyddad av en magisk sköld", + ["You are strengthened"] = "Du är förstärkt", + ["You are within a protection zone"] = "Du är inom en skyddszon", + ["You can enter new text."] = "Du kan skriva i ny text.", + ["You have %s percent"] = "Du har %s procent", + ["You have %s percent to go"] = "Du har %s procent kvar", + ["You may not logout during a fight"] = "Du kan ej logga ut i strid", + ["You may not logout or enter a protection zone"] = "Du kan ej logga ut eller gå in i en skyddszon", + ["You must enter a comment."] = "Du måste skriva en kommentar", + ["You must enter a valid server address and port."] = "Du måste fylla i en giltig server adress och port", + ["You must select a character to login!"] = "Du måste välja en karaktär för att logga in!", + ["Your Capacity:"] = "Din Kapacitet:", + ["You read the following, written by \n%s\n"] = "Du läser följande, Skrivet av \n%s\n", + ["You read the following, written on \n%s.\n"] = false, + ["Your Money:"] = "Dina Pengar:", + } +} + +modules.client_locales.installLocale(locale) \ No newline at end of file diff --git a/800OTClient/data/shaders/map_default_fragment.frag b/800OTClient/data/shaders/map_default_fragment.frag new file mode 100644 index 0000000..4e7785a --- /dev/null +++ b/800OTClient/data/shaders/map_default_fragment.frag @@ -0,0 +1,11 @@ + +varying vec2 v_TexCoord; +uniform vec4 u_Color; +uniform sampler2D u_Tex0; + +void main() +{ + gl_FragColor = texture2D(u_Tex0, v_TexCoord) * u_Color; + if(gl_FragColor.a < 0.01) + discard; +} diff --git a/800OTClient/data/shaders/map_default_vertex.frag b/800OTClient/data/shaders/map_default_vertex.frag new file mode 100644 index 0000000..0d5f690 --- /dev/null +++ b/800OTClient/data/shaders/map_default_vertex.frag @@ -0,0 +1,14 @@ + +attribute vec2 a_Vertex; +attribute vec2 a_TexCoord; + +uniform mat3 u_TransformMatrix; +uniform mat3 u_ProjectionMatrix; +uniform mat3 u_TextureMatrix; +varying vec2 v_TexCoord; + +void main() +{ + gl_Position = vec4((u_ProjectionMatrix * u_TransformMatrix * vec3(a_Vertex.xy, 1.0)).xy, 1.0, 1.0); + v_TexCoord = (u_TextureMatrix * vec3(a_TexCoord,1.0)).xy; +} diff --git a/800OTClient/data/shaders/map_rainbow_fragment.frag b/800OTClient/data/shaders/map_rainbow_fragment.frag new file mode 100644 index 0000000..6757750 --- /dev/null +++ b/800OTClient/data/shaders/map_rainbow_fragment.frag @@ -0,0 +1,15 @@ + +varying vec2 v_TexCoord; +varying vec2 v_TexCoord2; + +uniform vec4 u_Color; +uniform sampler2D u_Tex0; +uniform sampler2D u_Tex1; + +void main() +{ + gl_FragColor = texture2D(u_Tex0, v_TexCoord) * u_Color; + gl_FragColor += texture2D(u_Tex1, v_TexCoord2); + if(gl_FragColor.a < 0.01) + discard; +} diff --git a/800OTClient/data/shaders/map_rainbow_vertex.frag b/800OTClient/data/shaders/map_rainbow_vertex.frag new file mode 100644 index 0000000..f38475b --- /dev/null +++ b/800OTClient/data/shaders/map_rainbow_vertex.frag @@ -0,0 +1,33 @@ +attribute vec2 a_TexCoord; +attribute vec2 a_Vertex; + +varying vec2 v_TexCoord; +varying vec2 v_TexCoord2; + +uniform mat3 u_TextureMatrix; +uniform mat3 u_TransformMatrix; +uniform mat3 u_ProjectionMatrix; + +uniform vec2 u_Offset; +uniform vec2 u_Center; +uniform float u_Time; + +vec2 effectTextureSize = vec2(466.0, 342.0); +vec2 direction = vec2(1.0,0.2); +float speed = 200.0; + +vec2 rotate(vec2 v, float a) { + float s = sin(a); + float c = cos(a); + mat2 m = mat2(c, -s, s, c); + return m * v; +} + +void main() +{ + gl_Position = vec4((u_ProjectionMatrix * u_TransformMatrix * vec3(a_Vertex.xy, 1.0)).xy, 1.0, 1.0); + v_TexCoord = (u_TextureMatrix * vec3(a_TexCoord,1.0)).xy; + + v_TexCoord2 = ((a_Vertex + direction * u_Time * speed) / effectTextureSize); +} + diff --git a/800OTClient/data/shaders/outfit_default_fragment.frag b/800OTClient/data/shaders/outfit_default_fragment.frag new file mode 100644 index 0000000..1f205d1 --- /dev/null +++ b/800OTClient/data/shaders/outfit_default_fragment.frag @@ -0,0 +1,17 @@ +uniform mat4 u_Color; +varying vec2 v_TexCoord; +varying vec2 v_TexCoord2; +uniform sampler2D u_Tex0; +void main() +{ + gl_FragColor = texture2D(u_Tex0, v_TexCoord); + vec4 texcolor = texture2D(u_Tex0, v_TexCoord2); + if(texcolor.r > 0.9) { + gl_FragColor *= texcolor.g > 0.9 ? u_Color[0] : u_Color[1]; + } else if(texcolor.g > 0.9) { + gl_FragColor *= u_Color[2]; + } else if(texcolor.b > 0.9) { + gl_FragColor *= u_Color[3]; + } + if(gl_FragColor.a < 0.01) discard; +} diff --git a/800OTClient/data/shaders/outfit_default_vertex.frag b/800OTClient/data/shaders/outfit_default_vertex.frag new file mode 100644 index 0000000..44f0d76 --- /dev/null +++ b/800OTClient/data/shaders/outfit_default_vertex.frag @@ -0,0 +1,16 @@ +attribute vec2 a_Vertex; +attribute vec2 a_TexCoord; +uniform mat3 u_TextureMatrix; +varying vec2 v_TexCoord; +varying vec2 v_TexCoord2; +uniform mat3 u_TransformMatrix; +uniform mat3 u_ProjectionMatrix; +uniform vec2 u_Offset; + +void main() +{ + gl_Position = vec4((u_ProjectionMatrix * u_TransformMatrix * vec3(a_Vertex.xy, 1.0)).xy, 1.0, 1.0); + v_TexCoord = (u_TextureMatrix * vec3(a_TexCoord,1.0)).xy; + v_TexCoord2 = (u_TextureMatrix * vec3(a_TexCoord + u_Offset,1.0)).xy; +} + diff --git a/800OTClient/data/shaders/outfit_rainbow_fragment.frag b/800OTClient/data/shaders/outfit_rainbow_fragment.frag new file mode 100644 index 0000000..3c884a8 --- /dev/null +++ b/800OTClient/data/shaders/outfit_rainbow_fragment.frag @@ -0,0 +1,16 @@ +uniform mat4 u_Color; +varying vec2 v_TexCoord; +varying vec2 v_TexCoord2; +varying vec2 v_TexCoord3; +uniform sampler2D u_Tex0; +uniform sampler2D u_Tex1; +void main() +{ + gl_FragColor = texture2D(u_Tex0, v_TexCoord); + vec4 texcolor = texture2D(u_Tex0, v_TexCoord2); + vec4 effectColor = texture2D(u_Tex1, v_TexCoord3); + if(texcolor.a > 0.1) { + gl_FragColor *= effectColor; + } + if(gl_FragColor.a < 0.01) discard; +} \ No newline at end of file diff --git a/800OTClient/data/shaders/outfit_rainbow_vertex.frag b/800OTClient/data/shaders/outfit_rainbow_vertex.frag new file mode 100644 index 0000000..ca554bd --- /dev/null +++ b/800OTClient/data/shaders/outfit_rainbow_vertex.frag @@ -0,0 +1,47 @@ +attribute vec2 a_TexCoord; +uniform mat3 u_TextureMatrix; +varying vec2 v_TexCoord; +varying vec2 v_TexCoord2; +varying vec2 v_TexCoord3; +attribute vec2 a_Vertex; +uniform mat3 u_TransformMatrix; +uniform mat3 u_ProjectionMatrix; +uniform vec2 u_Offset; +uniform vec2 u_Center; +uniform float u_Time; + +vec2 effectTextureSize = vec2(466.0, 342.0); +vec2 direction = vec2(1.0,0.2); +float speed = 200.0; + +vec2 rotate(vec2 v, float a) { + float s = sin(a); + float c = cos(a); + mat2 m = mat2(c, -s, s, c); + return m * v; +} + +void main() +{ + vec2 offset = direction * speed * u_Time; + gl_Position = vec4((u_ProjectionMatrix * u_TransformMatrix * vec3(a_Vertex.xy, 1.0)).xy, 1.0, 1.0); + v_TexCoord = (u_TextureMatrix * vec3(a_TexCoord,1.0)).xy; + v_TexCoord2 = (u_TextureMatrix * vec3(a_TexCoord + u_Offset,1.0)).xy; + + vec2 vertex = a_Vertex; + if(vertex.x < u_Center.x) { + vertex.x = effectTextureSize.x / 10.0; + } + if(vertex.x > u_Center.x) { + vertex.x = effectTextureSize.x - effectTextureSize.x / 10.0; + } + if(vertex.y < u_Center.y) { + vertex.y = effectTextureSize.y / 10.0; + } + if(vertex.y > u_Center.y) { + vertex.y = effectTextureSize.y - effectTextureSize.y / 10.0; + } + + v_TexCoord3 = ((vertex + direction * u_Time * speed) / effectTextureSize); +} + diff --git a/800OTClient/data/sounds/Creature_Detected.ogg b/800OTClient/data/sounds/Creature_Detected.ogg new file mode 100644 index 0000000..fd36978 Binary files /dev/null and b/800OTClient/data/sounds/Creature_Detected.ogg differ diff --git a/800OTClient/data/sounds/Low_Health.ogg b/800OTClient/data/sounds/Low_Health.ogg new file mode 100644 index 0000000..d4b1062 Binary files /dev/null and b/800OTClient/data/sounds/Low_Health.ogg differ diff --git a/800OTClient/data/sounds/Low_Mana.ogg b/800OTClient/data/sounds/Low_Mana.ogg new file mode 100644 index 0000000..e2d7e1d Binary files /dev/null and b/800OTClient/data/sounds/Low_Mana.ogg differ diff --git a/800OTClient/data/sounds/Player_Attack.ogg b/800OTClient/data/sounds/Player_Attack.ogg new file mode 100644 index 0000000..84e9ef4 Binary files /dev/null and b/800OTClient/data/sounds/Player_Attack.ogg differ diff --git a/800OTClient/data/sounds/Player_Detected.ogg b/800OTClient/data/sounds/Player_Detected.ogg new file mode 100644 index 0000000..34de63e Binary files /dev/null and b/800OTClient/data/sounds/Player_Detected.ogg differ diff --git a/800OTClient/data/sounds/Private_Message.ogg b/800OTClient/data/sounds/Private_Message.ogg new file mode 100644 index 0000000..f3e5984 Binary files /dev/null and b/800OTClient/data/sounds/Private_Message.ogg differ diff --git a/800OTClient/data/sounds/alarm.ogg b/800OTClient/data/sounds/alarm.ogg new file mode 100644 index 0000000..a45d4c8 Binary files /dev/null and b/800OTClient/data/sounds/alarm.ogg differ diff --git a/800OTClient/data/sounds/magnum.ogg b/800OTClient/data/sounds/magnum.ogg new file mode 100644 index 0000000..d910ca1 Binary files /dev/null and b/800OTClient/data/sounds/magnum.ogg differ diff --git a/800OTClient/data/styles/10-buttons.otui b/800OTClient/data/styles/10-buttons.otui new file mode 100644 index 0000000..408e0c4 --- /dev/null +++ b/800OTClient/data/styles/10-buttons.otui @@ -0,0 +1,106 @@ +Button < UIButton + font: verdana-11px-antialised + color: #dfdfdfff + size: 106 23 + text-offset: 0 1 + image-source: /images/ui/button + image-color: #dfdfdf + image-clip: 0 0 22 23 + image-border: 3 + padding: 5 10 5 10 + opacity: 1.0 + change-cursor-image: true + cursor: pointer + + $hover !disabled: + image-clip: 0 23 22 23 + + $pressed: + image-clip: 0 46 22 23 + text-offset: 1 1 + + $disabled: + color: #dfdfdf88 + opacity: 0.8 + change-cursor-image: false + +TabButton < UIButton + size: 22 23 + image-source: /images/ui/tabbutton_rounded + image-color: #dfdfdf + image-clip: 0 0 22 23 + image-border: 3 + text-offset: 0 1 + icon-color: #dfdfdf + color: #dfdfdf + change-cursor-image: true + cursor: pointer + + $hover !on: + image-clip: 0 23 22 23 + color: #dfdfdf + + $disabled: + image-color: #dfdfdf66 + icon-color: #dfdfdf + change-cursor-image: false + + $on: + image-clip: 0 46 22 23 + color: #dfdfdf + +NextButton < UIButton + size: 12 21 + image-source: /images/ui/arrow_horizontal + image-clip: 12 0 12 21 + image-color: #ffffff + text-offset: 0 1 + change-cursor-image: true + cursor: pointer + + $hover !disabled: + image-clip: 12 21 12 21 + + $pressed: + image-clip: 12 21 12 21 + + $disabled: + image-color: #dfdfdf88 + change-cursor-image: false + +PreviousButton < UIButton + size: 12 21 + image-source: /images/ui/arrow_horizontal + image-clip: 0 0 12 21 + image-color: #ffffff + text-offset: 0 1 + change-cursor-image: true + cursor: pointer + + $hover !disabled: + image-clip: 0 21 12 21 + + $pressed: + image-clip: 0 21 12 21 + + $disabled: + image-color: #dfdfdf88 + change-cursor-image: false + +AddButton < UIButton + size: 20 20 + image-source: /images/ui/icon_add + image-color: #dfdfdfff + text-offset: 0 1 + change-cursor-image: true + cursor: pointer + + $hover !disabled: + image-color: #dfdfdf99 + + $pressed: + image-color: #dfdfdf44 + + $disabled: + image-color: #dfdfdf55 + change-cursor-image: false diff --git a/800OTClient/data/styles/10-checkboxes.otui b/800OTClient/data/styles/10-checkboxes.otui new file mode 100644 index 0000000..2c527b7 --- /dev/null +++ b/800OTClient/data/styles/10-checkboxes.otui @@ -0,0 +1,70 @@ +CheckBox < UICheckBox + size: 16 16 + text-align: left + text-offset: 18 1 + color: #dfdfdf + image-color: #dfdfdfff + image-rect: 0 0 15 15 + image-source: /images/ui/checkbox + change-cursor-image: true + cursor: pointer + + $hover !disabled: + color: #ffffff + + $!checked: + image-clip: 0 0 15 15 + + $hover !checked: + image-clip: 0 15 15 15 + + $checked: + image-clip: 0 30 15 15 + + $hover checked: + image-clip: 0 45 15 15 + + $disabled: + image-color: #dfdfdf88 + color: #dfdfdf88 + opacity: 0.8 + change-cursor-image: false + +ColorBox < UICheckBox + size: 16 16 + image-color: #dfdfdfff + image-source: /images/ui/colorbox + + $checked: + image-clip: 16 0 16 16 + + $!checked: + image-clip: 0 0 16 16 + +ButtonBox < UICheckBox + font: verdana-11px-antialised + color: #dfdfdfff + size: 106 23 + text-offset: 0 0 + text-align: center + image-source: /images/ui/button + image-color: #dfdfdf + image-clip: 0 0 22 23 + image-border: 3 + change-cursor-image: true + cursor: pointer + + $hover !disabled: + image-clip: 0 23 22 23 + + $checked: + image-clip: 0 46 22 23 + color: #dfdfdf + + $disabled: + color: #dfdfdf88 + image-color: #dfdfdf88 + change-cursor-image: false + +ButtonBoxRounded < ButtonBox + image-source: /images/ui/button_rounded \ No newline at end of file diff --git a/800OTClient/data/styles/10-comboboxes.otui b/800OTClient/data/styles/10-comboboxes.otui new file mode 100644 index 0000000..a4ad9da --- /dev/null +++ b/800OTClient/data/styles/10-comboboxes.otui @@ -0,0 +1,106 @@ +ComboBoxPopupScrollMenuButton < UIButton + height: 23 + font: verdana-11px-antialised + text-align: left + text-offset: 4 0 + color: #dfdfdf + background-color: alpha + margin: 1 + + $hover !disabled: + color: #dfdfdf + background-color: #355d89 + + $disabled: + color: #dfdfdf88 + +ComboBoxPopupScrollMenu < UIPopupScrollMenu + image-source: /images/ui/combobox_square + image-clip: 0 69 91 23 + image-border: 1 + +ComboBoxPopupMenuButton < UIButton + height: 23 + font: verdana-11px-antialised + text-align: left + text-offset: 4 0 + color: #dfdfdf + background-color: alpha + margin: 1 + + $hover !disabled: + color: #dfdfdf + background-color: #355d89 + + $disabled: + color: #dfdfdf88 + +ComboBoxPopupMenu < UIPopupMenu + image-source: /images/ui/combobox_square + image-clip: 0 69 91 23 + image-border: 1 + +ComboBox < UIComboBox + font: verdana-11px-antialised + color: #dfdfdf + size: 91 23 + text-offset: 3 0 + text-align: left + image-source: /images/ui/combobox_square + image-border: 3 + image-border-right: 19 + image-clip: 0 0 91 23 + + $hover !disabled: + image-clip: 0 23 91 23 + + $on: + image-clip: 0 46 91 23 + + $disabled: + color: #dfdfdf88 + opacity: 0.8 + +ComboBoxRoundedPopupScrollMenuButton < UIButton + height: 23 + font: verdana-11px-antialised + text-align: left + text-offset: 4 0 + color: #dfdfdf + background-color: alpha + + $hover !disabled: + color: #ffffff + background-color: #355d89 + + $disabled: + color: #dfdfdf88 + +ComboBoxRoundedPopupScrollMenu < UIPopupScrollMenu + image-source: /images/ui/combobox_rounded + image-clip: 0 69 91 23 + image-border: 3 + +ComboBoxRoundedPopupMenuButton < UIButton + height: 23 + font: verdana-11px-antialised + text-align: left + text-offset: 4 0 + color: #dfdfdf + background-color: alpha + + $hover !disabled: + color: #ffffff + background-color: #355d89 + + $disabled: + color: #dfdfdf88 + +ComboBoxRoundedPopupMenu < UIPopupMenu + image-source: /images/ui/combobox_rounded + image-clip: 0 69 91 23 + image-border: 3 + +ComboBoxRounded < ComboBox + image-source: /images/ui/combobox_rounded + image-border: 3 diff --git a/800OTClient/data/styles/10-creaturebuttons.otui b/800OTClient/data/styles/10-creaturebuttons.otui new file mode 100644 index 0000000..d96356c --- /dev/null +++ b/800OTClient/data/styles/10-creaturebuttons.otui @@ -0,0 +1,49 @@ +CreatureButton < UICreatureButton + height: 20 + margin-bottom: 5 + + UICreature + id: creature + size: 22 22 + anchors.left: parent.left + anchors.top: parent.top + phantom: true + + UIWidget + id: spacer + width: 3 + anchors.left: creature.right + anchors.top: creature.top + phantom: true + + UIWidget + id: skull + height: 11 + anchors.left: spacer.right + anchors.top: spacer.top + phantom: true + + UIWidget + id: emblem + height: 11 + anchors.left: skull.right + anchors.top: creature.top + phantom: true + + Label + id: label + anchors.left: emblem.right + anchors.right: parent.right + anchors.top: creature.top + color: #888888 + margin-left: 2 + phantom: true + + LifeProgressBar + id: lifeBar + height: 5 + anchors.left: spacer.right + anchors.right: parent.right + anchors.top: label.bottom + margin-top: 2 + phantom: true diff --git a/800OTClient/data/styles/10-creatures.otui b/800OTClient/data/styles/10-creatures.otui new file mode 100644 index 0000000..c6664ec --- /dev/null +++ b/800OTClient/data/styles/10-creatures.otui @@ -0,0 +1,10 @@ +Creature < UICreature + size: 80 80 + padding: 1 + image-source: /images/ui/panel_flat + image-border: 1 + border-width: 1 + border-color: alpha + + $checked: + border-color: white diff --git a/800OTClient/data/styles/10-items.otui b/800OTClient/data/styles/10-items.otui new file mode 100644 index 0000000..18bfa29 --- /dev/null +++ b/800OTClient/data/styles/10-items.otui @@ -0,0 +1,10 @@ +Item < UIItem + size: 34 34 + padding: 1 + image-source: /images/ui/item + font: verdana-11px-rounded + border-color: white + color: white + + $disabled: + color: #646464 diff --git a/800OTClient/data/styles/10-labels.otui b/800OTClient/data/styles/10-labels.otui new file mode 100644 index 0000000..8edfbb1 --- /dev/null +++ b/800OTClient/data/styles/10-labels.otui @@ -0,0 +1,23 @@ +Label < UILabel + font: verdana-11px-antialised + color: #dfdfdf + + $disabled: + color: #dfdfdf88 + +FlatLabel < UILabel + font: verdana-11px-antialised + color: #dfdfdf + size: 86 20 + text-offset: 3 3 + image-source: /images/ui/panel_flat + image-border: 1 + + $disabled: + color: #dfdfdf88 + +MenuLabel < Label + +GameLabel < UILabel + font: verdana-11px-antialised + color: #dfdfdf diff --git a/800OTClient/data/styles/10-listboxes.otui b/800OTClient/data/styles/10-listboxes.otui new file mode 100644 index 0000000..d52ff2f --- /dev/null +++ b/800OTClient/data/styles/10-listboxes.otui @@ -0,0 +1,19 @@ +TextList < UIScrollArea + layout: verticalBox + border-width: 1 + border-color: #272727 + background-color: #636363 + padding: 1 + auto-focus: none + +HorizontalList < UIScrollArea + layout: horizontalBox + border-width: 1 + border-color: #272727 + background-color: #636363 + +VerticalList < UIScrollArea + layout: verticalBox + border-width: 1 + border-color: #272727 + background-color: #636363 \ No newline at end of file diff --git a/800OTClient/data/styles/10-panels.otui b/800OTClient/data/styles/10-panels.otui new file mode 100644 index 0000000..15fbbc3 --- /dev/null +++ b/800OTClient/data/styles/10-panels.otui @@ -0,0 +1,19 @@ +Panel < UIWidget + phantom: true + auto-focus: first + +ScrollablePanel < UIScrollArea + phantom: true + auto-focus: first + +FlatPanel < Panel + image-source: /images/ui/panel_flat + image-border: 1 + +ScrollableFlatPanel < ScrollablePanel + image-source: /images/ui/panel_flat + image-border: 1 + +LightFlatPanel < Panel + image-source: /images/ui/panel_lightflat + image-border: 1 diff --git a/800OTClient/data/styles/10-progressbars.otui b/800OTClient/data/styles/10-progressbars.otui new file mode 100644 index 0000000..94b9b01 --- /dev/null +++ b/800OTClient/data/styles/10-progressbars.otui @@ -0,0 +1,37 @@ +ProgressBar < UIProgressBar + height: 16 + background-color: red + image-source: /images/ui/progressbar + image-border: 1 + font: verdana-11px-rounded + text-offset: 0 2 + + $!on: + visible: false + margin-top: 0 + margin-bottom: 0 + height: 0 + +ThickProgressBar < ProgressBar + image-source: /images/ui/progressbar_thick + +LifeProgressBar < UIProgressBar + height: 16 + background-color: green + border: 1 black + font: verdana-11px-rounded + text-offset: 0 2 + margin: 2 + +ProgressRect < UIProgressRect + anchors.fill: parent + phantom: true + color: white + background-color: #00000088 + font: verdana-11px-rounded + +HealthBar < ProgressBar + background-color: #ff4444 + +ManaBar < ProgressBar + background-color: #4444ff diff --git a/800OTClient/data/styles/10-scrollbars.otui b/800OTClient/data/styles/10-scrollbars.otui new file mode 100644 index 0000000..7f6b5d1 --- /dev/null +++ b/800OTClient/data/styles/10-scrollbars.otui @@ -0,0 +1,108 @@ +ScrollBarSlider < UIButton + id: sliderButton + anchors.centerIn: parent + size: 13 17 + image-source: /images/ui/scrollbar + image-clip: 0 26 13 13 + image-border: 2 + image-color: #ffffffff + $hover: + image-clip: 13 26 13 13 + $pressed: + image-clip: 26 26 13 13 + $disabled: + image-color: #ffffff66 + +ScrollBarValueLabel < Label + id: valueLabel + anchors.fill: parent + color: white + text-align: center + +VerticalScrollBar < UIScrollBar + orientation: vertical + width: 13 + height: 39 + image-source: /images/ui/scrollbar + image-clip: 39 0 13 65 + image-border: 1 + pixels-scroll: true + + UIButton + id: decrementButton + anchors.top: parent.top + anchors.left: parent.left + image-source: /images/ui/scrollbar + image-clip: 0 0 13 13 + image-color: #ffffffff + size: 13 13 + $hover: + image-clip: 13 0 13 13 + $pressed: + image-clip: 26 0 13 13 + $disabled: + image-color: #ffffff66 + + UIButton + id: incrementButton + anchors.bottom: parent.bottom + anchors.right: parent.right + size: 13 13 + image-source: /images/ui/scrollbar + image-clip: 0 13 13 13 + image-color: #ffffffff + $hover: + image-clip: 13 13 13 13 + $pressed: + image-clip: 26 13 13 13 + $disabled: + image-color: #ffffff66 + + ScrollBarSlider + + ScrollBarValueLabel + +HorizontalScrollBar < UIScrollBar + orientation: horizontal + height: 13 + width: 39 + image-source: /images/ui/scrollbar + image-clip: 0 65 52 13 + image-border: 1 + + $disabled: + color: #bbbbbb88 + + UIButton + id: decrementButton + anchors.top: parent.top + anchors.left: parent.left + image-source: /images/ui/scrollbar + image-clip: 0 39 13 13 + image-color: #ffffffff + size: 13 13 + $hover: + image-clip: 13 39 13 13 + $pressed: + image-clip: 26 39 13 13 + $disabled: + image-color: #ffffff66 + + UIButton + id: incrementButton + anchors.bottom: parent.bottom + anchors.right: parent.right + size: 13 13 + image-source: /images/ui/scrollbar + image-clip: 0 52 13 13 + image-color: #ffffffff + $hover: + image-clip: 13 52 13 13 + $pressed: + image-clip: 26 52 13 13 + $disabled: + image-color: #ffffff66 + + ScrollBarSlider + + ScrollBarValueLabel diff --git a/800OTClient/data/styles/10-separators.otui b/800OTClient/data/styles/10-separators.otui new file mode 100644 index 0000000..420d0fa --- /dev/null +++ b/800OTClient/data/styles/10-separators.otui @@ -0,0 +1,13 @@ +HorizontalSeparator < UIWidget + image-source: /images/ui/separator_horizontal + image-border: 1 + height: 2 + phantom: true + focusable: false + +VerticalSeparator < UIWidget + image-source: /images/ui/separator_vertical + image-border: 1 + width: 2 + phantom: true + focusable: false diff --git a/800OTClient/data/styles/10-splitters.otui b/800OTClient/data/styles/10-splitters.otui new file mode 100644 index 0000000..ca9a4f6 --- /dev/null +++ b/800OTClient/data/styles/10-splitters.otui @@ -0,0 +1,9 @@ +Splitter < UISplitter + size: 4 4 + opacity: 0 + background: #ffffff44 + +ResizeBorder < UIResizeBorder + size: 4 4 + opacity: 0 + background: #ffffff44 \ No newline at end of file diff --git a/800OTClient/data/styles/10-textedits.otui b/800OTClient/data/styles/10-textedits.otui new file mode 100644 index 0000000..2ec4c1c --- /dev/null +++ b/800OTClient/data/styles/10-textedits.otui @@ -0,0 +1,22 @@ +TextEdit < UITextEdit + font: verdana-11px-antialised + color: #272727 + size: 86 22 + text-offset: 0 4 + opacity: 1 + padding: 4 + image-source: /images/ui/textedit + image-border: 1 + selection-color: #272727 + selection-background-color: #cccccc + change-cursor-image: true + $disabled: + color: #27272788 + opacity: 0.5 + change-cursor-image: false + +PasswordTextEdit < TextEdit + text-hidden: true + +MultilineTextEdit < TextEdit + multiline: true diff --git a/800OTClient/data/styles/10-windows.otui b/800OTClient/data/styles/10-windows.otui new file mode 100644 index 0000000..9250784 --- /dev/null +++ b/800OTClient/data/styles/10-windows.otui @@ -0,0 +1,35 @@ +Window < UIWindow + font: verdana-11px-antialised + size: 200 200 + opacity: 1 + color: #dfdfdf + text-offset: 0 6 + text-align: top + image-source: /images/ui/window + image-border: 6 + image-border-top: 27 + padding-top: 36 + padding-left: 16 + padding-right: 16 + padding-bottom: 16 + + $disabled: + color: #dfdfdf88 + + $dragging: + opacity: 0.8 + +HeadlessWindow < UIWindow + image-source: /images/ui/window_headless + image-border: 5 + padding: 5 + +MainWindow < Window + anchors.centerIn: parent + +StaticWindow < Window + &static: true + +StaticMainWindow < StaticWindow + anchors.centerIn: parent + \ No newline at end of file diff --git a/800OTClient/data/styles/20-imageview.otui b/800OTClient/data/styles/20-imageview.otui new file mode 100644 index 0000000..73bda84 --- /dev/null +++ b/800OTClient/data/styles/20-imageview.otui @@ -0,0 +1,6 @@ +ImageView < UIImageView + image-smooth: false + image-fixed-ratio: true + draggable: true + border-width: 2 + border-color: #000000 diff --git a/800OTClient/data/styles/20-popupmenus.otui b/800OTClient/data/styles/20-popupmenus.otui new file mode 100644 index 0000000..d7b71fc --- /dev/null +++ b/800OTClient/data/styles/20-popupmenus.otui @@ -0,0 +1,83 @@ +PopupMenuButton < UIButton + height: 18 + size: 0 21 + text-offset: 4 0 + text-align: left + font: verdana-11px-antialised + + color: #aaaaaa + background-color: alpha + + $hover !disabled: + color: #ffffff + background-color: #ffffff44 + image-clip: 0 40 20 20 + + $disabled: + color: #555555 + +PopupMenuShortcutLabel < Label + font: verdana-11px-antialised + text-align: right + anchors.fill: parent + margin-right: 2 + margin-left: 5 + +PopupMenuSeparator < UIWidget + margin-left: 2 + margin-right: 2 + margin-bottom: 1 + image-source: /images/ui/menubox + image-border-left: 1 + image-border-right: 1 + image-clip: 0 0 32 2 + height: 2 + phantom: true + +PopupMenu < UIPopupMenu + width: 50 + image-source: /images/ui/menubox + image-border: 3 + padding: 3 + +PopupScrollMenuButton < UIButton + height: 18 + size: 0 21 + text-offset: 4 0 + text-align: left + font: verdana-11px-antialised + + color: #aaaaaa + background-color: alpha + + $hover !disabled: + color: #ffffff + background-color: #ffffff44 + image-clip: 0 40 20 20 + + $disabled: + color: #555555 + +PopupScrollMenuShortcutLabel < Label + font: verdana-11px-antialised + text-align: right + anchors.fill: parent + margin-right: 2 + margin-left: 5 + +PopupScrollMenuSeparator < UIWidget + margin-left: 2 + margin-right: 2 + margin-bottom: 1 + image-source: /images/ui/menubox + image-border-left: 1 + image-border-right: 1 + image-clip: 0 0 32 2 + height: 2 + phantom: true + +PopupScrollMenu < UIPopupScrollMenu + width: 50 + image-source: /images/ui/menubox + image-border: 3 + padding: 3 diff --git a/800OTClient/data/styles/20-smallscrollbar.otui b/800OTClient/data/styles/20-smallscrollbar.otui new file mode 100644 index 0000000..4396a0b --- /dev/null +++ b/800OTClient/data/styles/20-smallscrollbar.otui @@ -0,0 +1,60 @@ +SmallScrollBar < UIScrollBar + orientation: vertical + margin-bottom: 1 + step: 20 + width: 8 + image-source: /images/ui/scrollbar + image-clip: 39 0 13 65 + image-border: 1 + pixels-scroll: true + + UIButton + id: decrementButton + anchors.top: parent.top + anchors.left: parent.left + image-source: /images/ui/scrollbar + image-clip: 0 0 13 13 + image-color: #ffffffff + size: 8 8 + $hover: + image-clip: 13 0 13 13 + $pressed: + image-clip: 26 0 13 13 + $disabled: + image-color: #ffffff66 + + UIButton + id: incrementButton + anchors.bottom: parent.bottom + anchors.right: parent.right + size: 8 8 + image-source: /images/ui/scrollbar + image-clip: 0 13 13 13 + image-color: #ffffffff + $hover: + image-clip: 13 13 13 13 + $pressed: + image-clip: 26 13 13 13 + $disabled: + image-color: #ffffff66 + + UIButton + id: sliderButton + anchors.centerIn: parent + size: 8 11 + image-source: /images/ui/scrollbar + image-clip: 0 26 13 13 + image-border: 2 + image-color: #ffffffff + $hover: + image-clip: 13 26 13 13 + $pressed: + image-clip: 26 26 13 13 + $disabled: + image-color: #ffffff66 + + Label + id: valueLabel + anchors.fill: parent + color: white + text-align: center \ No newline at end of file diff --git a/800OTClient/data/styles/20-spinboxes.otui b/800OTClient/data/styles/20-spinboxes.otui new file mode 100644 index 0000000..1ceddf4 --- /dev/null +++ b/800OTClient/data/styles/20-spinboxes.otui @@ -0,0 +1,34 @@ +SpinBox < TextEdit + __class: UISpinBox + text-align: left + size: 86 22 + padding: 0 + padding-left: 2 + + Button + id: up + size: 11 11 + image-source: /images/ui/spinbox_up + image-border: 1 + image-clip: 0 0 10 10 + anchors.top: parent.top + anchors.right: parent.right + + $hover: + image-clip: 0 10 10 10 + $pressed: + image-clip: 0 20 10 10 + + Button + id: down + size: 11 11 + image-source: /images/ui/spinbox_down + image-border: 1 + image-clip: 0 0 10 10 + anchors.bottom: parent.bottom + anchors.right: parent.right + + $hover: + image-clip: 0 10 10 10 + $pressed: + image-clip: 0 20 10 10 diff --git a/800OTClient/data/styles/20-tabbars.otui b/800OTClient/data/styles/20-tabbars.otui new file mode 100644 index 0000000..7e6b9b9 --- /dev/null +++ b/800OTClient/data/styles/20-tabbars.otui @@ -0,0 +1,131 @@ +MoveableTabBar < UIMoveableTabBar + size: 80 21 +MoveableTabBarPanel < Panel +MoveableTabBarButton < UIButton + size: 20 21 + image-source: /images/ui/tabbutton_square + image-color: #dfdfdf + image-clip: 0 0 20 21 + image-border: 3 + image-border-bottom: 0 + icon-color: #dfdfdf + color: #dfdfdf + anchors.top: parent.top + anchors.left: parent.left + padding: 5 + + $hover !checked: + image-clip: 0 21 20 21 + color: #dfdfdf + + $disabled: + image-color: #dfdfdf88 + icon-color: #dfdfdf + + $checked: + image-clip: 0 42 20 21 + color: #dfdfdf + + $on !checked: + color: #de6f6f + +TabBar < UITabBar + size: 80 21 + Panel + id: buttonsPanel + anchors.fill: parent +TabBarPanel < Panel +TabBarButton < UIButton + size: 20 21 + image-source: /images/ui/tabbutton_square + image-source: /images/ui/tabbutton_square + image-color: #dfdfdf + image-clip: 0 0 20 21 + image-border: 3 + image-border-bottom: 0 + icon-color: #dfdfdf + color: #dfdfdf + anchors.top: parent.top + padding: 5 + + $first: + anchors.left: parent.left + + $!first: + anchors.left: prev.right + margin-left: 5 + + $hover !checked: + image-clip: 0 21 20 21 + color: #dfdfdf + + $disabled: + image-color: #dfdfdf88 + icon-color: #dfdfdf + + $checked: + image-clip: 0 42 20 21 + color: #dfdfdf + + $on !checked: + color: #dfdfdf + +TabBarRounded < TabBar +TabBarRoundedPanel < TabBarPanel +TabBarRoundedButton < TabBarButton + image-source: /images/ui/tabbutton_rounded + size: 22 23 + image-clip: 0 0 22 23 + + $hover !checked: + image-clip: 0 23 22 23 + + $checked: + image-clip: 0 46 22 23 + +TabBarVertical < UITabBar + width: 96 + ScrollableFlatPanel + id: buttonsPanel + anchors.top: parent.top + anchors.left: parent.left + anchors.right: scrollBar.left + anchors.bottom: parent.bottom + vertical-scrollbar: scrollBar + margin-right: 1 + padding-top: 10 + + VerticalScrollBar + id: scrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 16 + pixels-scroll: true + $!on: + width: 0 +TabBarVerticalPanel < Panel +TabBarVerticalButton < UIButton + size: 48 48 + color: #aaaaaa + anchors.left: parent.left + anchors.right: parent.right + text-align: bottom + icon-align: top + icon-offset-y: 2 + icon-color: #888888 + $first: + anchors.top: parent.top + $!first: + anchors.top: prev.bottom + margin-top: 10 + $hover !checked: + color: white + icon-color: #dfdfdf + $disabled: + icon-color: #333333 + $checked: + icon-color: #ffffff + color: #80c7f8 + $on !checked: + color: #F55E5E diff --git a/800OTClient/data/styles/20-tables.otui b/800OTClient/data/styles/20-tables.otui new file mode 100644 index 0000000..51e53e5 --- /dev/null +++ b/800OTClient/data/styles/20-tables.otui @@ -0,0 +1,62 @@ +Table < UITable + layout: verticalBox + header-column-style: TableHeaderColumn + header-row-style: TableHeaderRow + column-style: TableColumn + row-style: TableRow + +TableData < UIScrollArea + layout: verticalBox + +TableRow < UITableRow + layout: horizontalBox + height: 10 + text-wrap: true + focusable: true + even-background-color: alpha + odd-background-color: #00000022 + + $focus: + background-color: #294f6d + color: #ffffff + +TableColumn < Label + width: 30 + text-wrap: true + focusable: false + +TableHeaderRow < Label + layout: horizontalBox + focusable: false + height: 10 + text-wrap: true + +TableHeaderColumn < UITableHeaderColumn + font: verdana-11px-antialised + background-color: alpha + color: #dfdfdfff + height: 23 + focusable: true + text-offset: 0 0 + image-source: /images/ui/button + image-color: #dfdfdf + image-clip: 0 0 22 23 + image-border: 3 + padding: 5 10 5 10 + enabled: false + focusable: false + + $hover !disabled: + image-clip: 0 23 22 23 + + $pressed: + image-clip: 0 46 22 23 + text-offset: 1 1 + + $disabled: + color: #dfdfdf88 + opacity: 0.8 + +SortableTableHeaderColumn < TableHeaderColumn + enabled: true + focusable: true \ No newline at end of file diff --git a/800OTClient/data/styles/20-topmenu.otui b/800OTClient/data/styles/20-topmenu.otui new file mode 100644 index 0000000..cfed71a --- /dev/null +++ b/800OTClient/data/styles/20-topmenu.otui @@ -0,0 +1,116 @@ +TopButton < UIButton + size: 26 26 + image-source: /images/ui/button_top + image-clip: 0 0 26 26 + image-border: 3 + image-color: #ffffffff + icon-color: #ffffffff + + $on: + image-source: /images/ui/button_top_blink + + $hover !disabled: + image-color: #ffffff99 + image-clip: 26 0 26 26 + + $pressed: + image-clip: 52 0 26 26 + + $disabled: + image-color: #ffffff44 + icon-color: #ffffff44 + +TopToggleButton < UIButton + size: 26 26 + image-source: /images/ui/button_top + image-clip: 0 0 26 26 + image-color: #ffffffff + image-border: 3 + icon-color: #ffffffff + + $hover !disabled: + image-color: #ffffff99 + image-clip: 26 0 26 26 + + $pressed: + image-clip: 52 0 26 26 + + $disabled: + image-color: #ffffff44 + icon-color: #ffffff44 + +TopMenuButtonsPanel < Panel + layout: + type: horizontalBox + spacing: 4 + fit-children: true + padding: 6 4 + +TopMenuPanel < Panel + height: 36 + image-source: /images/ui/panel_top + image-repeated: true + focusable: false + +TopMenuFrameCounterLabel < Label + font: verdana-11px-rounded + color: white + margin-top: 4 + margin-left: 5 + +TopMenuPingLabel < Label + font: verdana-11px-rounded + +TopMenu < TopMenuPanel + id: topMenu + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + TopMenuButtonsPanel + id: leftButtonsPanel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + + TopMenuButtonsPanel + id: leftGameButtonsPanel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: prev.right + visible: false + + TopMenuFrameCounterLabel + id: fpsLabel + text-auto-resize: true + anchors.top: parent.top + anchors.left: leftGameButtonsPanel.right + + TopMenuPingLabel + color: white + id: pingLabel + text-auto-resize: true + anchors.top: fpsLabel.bottom + anchors.left: fpsLabel.left + + Label + id: onlineLabel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + font: verdana-11px-antialised + text-align: center + text-auto-resize: true + + TopMenuButtonsPanel + id: rightButtonsPanel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + + TopMenuButtonsPanel + id: rightGameButtonsPanel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: prev.left + visible: false \ No newline at end of file diff --git a/800OTClient/data/styles/30-inputboxes.otui b/800OTClient/data/styles/30-inputboxes.otui new file mode 100644 index 0000000..90cbb65 --- /dev/null +++ b/800OTClient/data/styles/30-inputboxes.otui @@ -0,0 +1,30 @@ +InputBoxLabel < Label + fixed-size: true + text-align: left +InputBoxLineEdit < TextEdit +InputBoxTextEdit < MultilineTextEdit + text-wrap: true +InputBoxSpinBox < SpinBox +InputBoxCheckBox < CheckBox +InputBoxComboBox < ComboBox +InputBoxComboBoxPopupMenu < ComboBoxPopupMenu +InputBoxComboBoxPopupMenuButton < ComboBoxPopupMenuButton +InputBoxButton < Button + margin-left: 10 + fixed-size: true + +InputBoxButtonsPanel < Panel + height: 20 + margin-top: 4 + focusable: false + layout: + type: horizontalBox + align-right: true + +InputBoxWindow < MainWindow + __class: UIInputBox + width: 260 + layout: + type: verticalBox + fit-children: true + spacing: 2 \ No newline at end of file diff --git a/800OTClient/data/styles/30-messageboxes.otui b/800OTClient/data/styles/30-messageboxes.otui new file mode 100644 index 0000000..04fcdf6 --- /dev/null +++ b/800OTClient/data/styles/30-messageboxes.otui @@ -0,0 +1,15 @@ +MessageBoxLabel < Label + id: messageBoxLabel + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + text-wrap: true + text-auto-resize: true + +MessageBoxButtonHolder < UIWidget + id: buttonHolder + margin-top: 10 + anchors.bottom: parent.bottom + +MessageBoxButton < Button + margin-left: 10 + width: 80 diff --git a/800OTClient/data/styles/30-miniwindow.otui b/800OTClient/data/styles/30-miniwindow.otui new file mode 100644 index 0000000..2e4a363 --- /dev/null +++ b/800OTClient/data/styles/30-miniwindow.otui @@ -0,0 +1,128 @@ +MiniWindow < UIMiniWindow + font: verdana-11px-antialised + icon-rect: 4 4 16 16 + width: 192 + height: 200 + text-offset: 24 5 + text-align: topLeft + image-source: /images/ui/miniwindow + image-border: 4 + image-border-top: 23 + image-border-bottom: 4 + focusable: false + &minimizedHeight: 24 + + $on: + image-border-bottom: 2 + + UIWidget + id: miniwindowTopBar + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + margin-right: 3 + margin-left: 3 + margin-top: 3 + size: 258 18 + phantom: true + + UIButton + id: closeButton + anchors.top: parent.top + anchors.right: parent.right + margin-top: 5 + margin-right: 5 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 28 0 14 14 + + $hover: + image-clip: 28 14 14 14 + + $pressed: + image-clip: 28 28 14 14 + + UIButton + id: minimizeButton + anchors.top: closeButton.top + anchors.right: closeButton.left + margin-right: 3 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 0 0 14 14 + + $hover: + image-clip: 0 14 14 14 + + $pressed: + image-clip: 0 28 14 14 + + $on: + image-clip: 14 0 14 14 + + $on hover: + image-clip: 14 14 14 14 + + $on pressed: + image-clip: 14 28 14 14 + + UIButton + id: lockButton + anchors.top: minimizeButton.top + anchors.right: minimizeButton.left + margin-right: 3 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 112 0 14 14 + + $hover: + image-clip: 112 14 14 14 + + $pressed: + image-clip: 112 28 14 14 + + $on: + image-clip: 98 0 14 14 + + $on hover: + image-clip: 98 14 14 14 + + $on pressed: + image-clip: 98 28 14 14 + + VerticalScrollBar + id: miniwindowScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 14 + margin-top: 22 + margin-right: 3 + margin-bottom: 3 + pixels-scroll: true + + $!on: + width: 0 + + ResizeBorder + id: bottomResizeBorder + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 3 + minimum: 48 + margin-left: 3 + margin-right: 3 + background: #ffffff88 + +MiniWindowContents < ScrollablePanel + id: contentsPanel + anchors.fill: parent + anchors.right: miniwindowScrollBar.left + margin-left: 3 + margin-bottom: 3 + margin-top: 22 + margin-right: 1 + vertical-scrollbar: miniwindowScrollBar + +HeadlessMiniWindow < MiniWindow diff --git a/800OTClient/data/styles/40-console.otui b/800OTClient/data/styles/40-console.otui new file mode 100644 index 0000000..ed9e0ae --- /dev/null +++ b/800OTClient/data/styles/40-console.otui @@ -0,0 +1,186 @@ +ConsoleLabel < UITextEdit + font: verdana-11px-antialised + height: 14 + color: yellow + margin-left: 2 + text-wrap: true + text-auto-resize: true + selection-color: #111416 + selection-background-color: #999999 + change-cursor-image: false + cursor-visible: false + editable: false + draggable: true + selectable: false + focusable: false + +ConsolePhantomLabel < UILabel + font: verdana-11px-antialised + height: 14 + color: yellow + text-wrap: true + text-auto-resize: true + selection-color: #111416 + selection-background-color: #999999 + +ConsoleTabBar < MoveableTabBar + height: 28 + +ConsoleTabBarPanel < MoveableTabBarPanel + id: consoleTab + + ScrollablePanel + id: consoleBuffer + anchors.fill: parent + margin-right: 12 + vertical-scrollbar: consoleScrollBar + layout: + type: verticalBox + align-bottom: true + border-width: 1 + border-color: #202327 + background: #00000066 + inverted-scroll: true + padding: 1 + + VerticalScrollBar + id: consoleScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 14 + pixels-scroll: true + +ConsoleTabBarButton < MoveableTabBarButton + height: 28 + padding: 15 + +ConsolePanel < Panel + image-source: /images/ui/panel_bottom + image-border: 4 + + $first: + anchors.fill: parent + + $!first: + anchors.top: prev.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + + CheckBox + id: toggleChat + !tooltip: tr('Disable chat mode, allow to walk using ASDW') + anchors.left: parent.left + anchors.top: parent.top + margin-left: 13 + margin-top: 8 + @onCheckChange: toggleChat() + + TabButton + id: prevChannelButton + icon: /images/game/console/leftarrow + anchors.left: toggleChat.right + anchors.top: parent.top + margin-left: 3 + margin-top: 6 + + ConsoleTabBar + id: consoleTabBar + anchors.left: prev.right + anchors.top: parent.top + anchors.right: next.left + margin-left: 5 + margin-top: 3 + margin-right: 5 + tab-spacing: 2 + movable: true + + TabButton + id: nextChannelButton + icon: /images/game/console/rightarrow + anchors.right: next.left + anchors.top: parent.top + margin-right: 5 + margin-top: 6 + + TabButton + id: closeChannelButton + !tooltip: tr('Close this channel') .. ' (Ctrl+E)' + icon: /images/game/console/closechannel + anchors.right: next.left + anchors.top: parent.top + enabled: false + margin-right: 5 + margin-top: 6 + @onClick: removeCurrentTab() + + TabButton + id: clearChannelButton + !tooltip: tr('Clear current message window') + icon: /images/game/console/clearchannel + anchors.right: next.left + anchors.top: parent.top + margin-right: 5 + margin-top: 6 + @onClick: | + local consoleTabBar = self:getParent():getChildById('consoleTabBar') + clearChannel(consoleTabBar) + + TabButton + id: channelsButton + !tooltip: tr('Open new channel') .. ' (Ctrl+O)' + icon: /images/game/console/channels + anchors.right: next.left + anchors.top: parent.top + margin-right: 5 + margin-top: 6 + @onClick: g_game.requestChannels() + + TabButton + id: ignoreButton + !tooltip: tr('Ignore players') + icon: /images/game/console/ignore + anchors.right: parent.right + anchors.top: parent.top + margin-right: 5 + margin-top: 6 + @onClick: onClickIgnoreButton() + + Panel + id: consoleContentPanel + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: consoleTextEdit.top + margin-left: 6 + margin-right: 6 + margin-bottom: 4 + margin-top: 4 + padding: 1 + focusable: false + phantom: true + + TabButton + id: sayModeButton + icon: /images/game/console/say + !tooltip: tr('Adjust volume') + &sayMode: 2 + size: 20 20 + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-left: 6 + margin-bottom: 6 + @onClick: sayModeChange() + + TextEdit + id: consoleTextEdit + anchors.left: sayModeButton.right + anchors.right: parent.right + anchors.bottom: parent.bottom + margin-right: 6 + margin-left: 6 + margin-bottom: 6 + shift-navigation: true + max-length: 255 + text-auto-submit: true diff --git a/800OTClient/data/styles/40-container.otui b/800OTClient/data/styles/40-container.otui new file mode 100644 index 0000000..ddfada8 --- /dev/null +++ b/800OTClient/data/styles/40-container.otui @@ -0,0 +1,74 @@ +PageButton < Button + size: 30 18 + margin: 1 + + +ContainerWindow < MiniWindow + height: 150 + &save: true + &containerWindow: true + + UIItem + id: containerItemWidget + virtual: true + size: 16 16 + anchors.top: parent.top + anchors.left: parent.left + margin-top: 1 + margin-left: 3 + + UIButton + id: upButton + anchors.top: lockButton.top + anchors.right: lockButton.left + margin-right: 3 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 42 0 14 14 + + $hover: + image-clip: 42 14 14 14 + + $pressed: + image-clip: 42 28 14 14 + + Panel + id: pagePanel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: miniwindowTopBar.bottom + margin: 1 3 0 3 + background: #00000066 + height: 20 + + $on: + visible: true + + $!on: + visible: false + + Label + id: pageLabel + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 2 + text-auto-resize: true + + PageButton + id: prevPageButton + text: < + anchors.top: parent.top + anchors.left: parent.left + + PageButton + id: nextPageButton + text: > + anchors.top: parent.top + anchors.right: parent.right + + MiniWindowContents + padding-right: 0 + layout: + type: grid + cell-size: 34 34 + flow: true diff --git a/800OTClient/data/styles/40-entergame.otui b/800OTClient/data/styles/40-entergame.otui new file mode 100644 index 0000000..71f850b --- /dev/null +++ b/800OTClient/data/styles/40-entergame.otui @@ -0,0 +1,3 @@ +EnterGameWindow < StaticMainWindow + !text: tr('Enter Game') + size: 260 354 \ No newline at end of file diff --git a/800OTClient/data/styles/40-gamebuttons.otui b/800OTClient/data/styles/40-gamebuttons.otui new file mode 100644 index 0000000..76c3b9e --- /dev/null +++ b/800OTClient/data/styles/40-gamebuttons.otui @@ -0,0 +1,2 @@ +GameButtonsWindow < MiniWindow + height: 26 diff --git a/800OTClient/data/styles/40-healthinfo.otui b/800OTClient/data/styles/40-healthinfo.otui new file mode 100644 index 0000000..f676a1c --- /dev/null +++ b/800OTClient/data/styles/40-healthinfo.otui @@ -0,0 +1,147 @@ +ExperienceBar < ProgressBar + id: experienceBar + background-color: #B6E866 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin: 1 + margin-top: 3 + +SoulLabel < GameLabel + id: soulLabel + text-align: right + color: white + font: verdana-11px-rounded + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.horizontalCenter + margin-top: 5 + margin-right: 3 + on: true + + $!on: + visible: false + margin-top: 0 + height: 0 + +CapLabel < GameLabel + id: capLabel + color: white + font: verdana-11px-rounded + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.horizontalCenter + margin-top: 5 + margin-left: 3 + on: true + + $!on: + visible: false + margin-top: 0 + height: 0 + +ConditionWidget < UIWidget + size: 18 18 + + $!first: + margin-left: 2 + +HealthOverlay < UIWidget + id: healthOverlay + anchors.fill: parent + phantom: true + + HealthBar + id: topHealthBar + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.horizontalCenter + phantom: true + + ManaBar + id: topManaBar + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.horizontalCenter + phantom: true + + UIProgressBar + id: healthCircle + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/left_empty + margin-right: 169 + margin-bottom: 16 + opacity: 0.5 + phantom: true + + UIProgressBar + id: healthCircleFront + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/left_full + margin-right: 169 + margin-bottom: 16 + opacity: 0.5 + phantom: true + + UIProgressBar + id: manaCircle + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/right_empty + image-auto-resize: true + margin-left: 130 + margin-bottom: 16 + opacity: 0.5 + phantom: true + + UIProgressBar + id: manaCircleFront + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/right_full + margin-left: 130 + margin-bottom: 16 + opacity: 0.4 + image-color: #0000FFFF + phantom: true + +HealthInfoWindow < MiniWindow + icon: /images/topbuttons/healthinfo + !text: tr('Health Info') + height: 123 + + MiniWindowContents + HealthBar + id: healthBar + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + margin: 2 + margin-top: 1 + + ManaBar + id: manaBar + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin: 2 + + ExperienceBar + Panel + id: conditionPanel + layout: + type: horizontalBox + height: 22 + margin-top: 4 + padding: 2 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + border-width: 1 + border-color: #00000077 + background-color: #ffffff11 + SoulLabel + CapLabel + diff --git a/800OTClient/data/styles/40-inventory.otui b/800OTClient/data/styles/40-inventory.otui new file mode 100644 index 0000000..f6fa1c5 --- /dev/null +++ b/800OTClient/data/styles/40-inventory.otui @@ -0,0 +1,299 @@ +InventoryItem < Item + $on: + image-source: /images/ui/item-blessed + +HeadSlot < InventoryItem + id: slot1 + image-source: /images/game/slots/head + &position: {x=65535, y=1, z=0} + $on: + image-source: /images/game/slots/head-blessed + +BodySlot < InventoryItem + id: slot4 + image-source: /images/game/slots/body + &position: {x=65535, y=4, z=0} + $on: + image-source: /images/game/slots/body-blessed + +LegSlot < InventoryItem + id: slot7 + image-source: /images/game/slots/legs + &position: {x=65535, y=7, z=0} + $on: + image-source: /images/game/slots/legs-blessed + +FeetSlot < InventoryItem + id: slot8 + image-source: /images/game/slots/feet + &position: {x=65535, y=8, z=0} + $on: + image-source: /images/game/slots/feet-blessed + +NeckSlot < InventoryItem + id: slot2 + image-source: /images/game/slots/neck + &position: {x=65535, y=2, z=0} + $on: + image-source: /images/game/slots/neck-blessed + +LeftSlot < InventoryItem + id: slot6 + image-source: /images/game/slots/left-hand + &position: {x=65535, y=6, z=0} + $on: + image-source: /images/game/slots/left-hand-blessed + +FingerSlot < InventoryItem + id: slot9 + image-source: /images/game/slots/finger + &position: {x=65535, y=9, z=0} + $on: + image-source: /images/game/slots/finger-blessed + +BackSlot < InventoryItem + id: slot3 + image-source: /images/game/slots/back + &position: {x=65535, y=3, z=0} + $on: + image-source: /images/game/slots/back-blessed + +RightSlot < InventoryItem + id: slot5 + image-source: /images/game/slots/right-hand + &position: {x=65535, y=5, z=0} + $on: + image-source: /images/game/slots/right-hand-blessed + +AmmoSlot < InventoryItem + id: slot10 + image-source: /images/game/slots/ammo + &position: {x=65535, y=10, z=0} + $on: + image-source: /images/game/slots/ammo-blessed + +PurseButton < UIButton + id: purseButton + size: 34 12 + !tooltip: tr('Open purse') + icon-source: /images/game/slots/purse + icon-clip: 0 0 34 12 + + $on: + icon-clip: 0 12 34 12 + + $pressed: + icon-clip: 0 12 34 12 + +CombatBox < UICheckBox + size: 20 20 + image-clip: 0 0 20 20 + margin-left: 4 + + $checked: + image-clip: 0 20 20 20 + + +InventoryButton < Button + font: verdana-11px-antialised + height: 18 + margin-top: 2 + text-align: center + +SoulCapLabel < GameLabel + text-align: center + color: #FFFFFF + font: cipsoftFont + margin-top: 4 + text-offset: 0 3 + width: 36 + height: 20 + icon-source: /images/game/slots/soulcap + +FightOffensiveBox < CombatBox + image-source: /images/game/combatmodes/fightoffensive +FightBalancedBox < CombatBox + image-source: /images/game/combatmodes/fightbalanced +FightDefensiveBox < CombatBox + image-source: /images/game/combatmodes/fightdefensive +ChaseModeBox < CombatBox + image-source: /images/game/combatmodes/chasemode +SafeFightBox < CombatBox + image-source: /images/game/combatmodes/safefight + +MountButton < CombatBox + image-source: /images/game/combatmodes/mount + +InventoryWindow < MiniWindow + icon: /images/topbuttons/inventory + height: 200 + id: inventoryWindow + @onClose: modules.game_inventory.onMiniWindowClose() + &save: true + &autoOpen: 3 + + MiniWindowContents + anchors.left: parent.left + + Panel + id: inventoryPanel + margin-right: 63 + margin-top: 2 + anchors.fill: parent + + HeadSlot + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 3 + + BodySlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + LegSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + FeetSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + NeckSlot + anchors.top: slot1.top + anchors.right: slot1.left + margin-top: 13 + margin-right: 5 + + LeftSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + FingerSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + BackSlot + anchors.top: slot1.top + anchors.left: slot1.right + margin-top: 13 + margin-left: 5 + + RightSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + AmmoSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + SoulCapLabel + id: soulLabel + anchors.top: slot10.bottom + anchors.horizontalCenter: slot10.horizontalCenter + + SoulCapLabel + id: capLabel + anchors.top: slot9.bottom + anchors.horizontalCenter: slot9.horizontalCenter + + PurseButton + anchors.left: slot3.left + anchors.bottom: slot3.top + margin-bottom: 3 + + Panel + id: conditionPanel + layout: + type: horizontalBox + height: 22 + padding: 2 + anchors.top: slot8.bottom + anchors.left: slot6.left + anchors.right: slot5.right + margin-top: 4 + border-width: 1 + border-color: #00000077 + background-color: #ffffff22 + + Panel + margin-top: 5 + anchors.fill: parent + anchors.left: prev.right + + FightOffensiveBox + id: fightOffensiveBox + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + ChaseModeBox + id: chaseModeBox + anchors.left: prev.right + anchors.top: parent.top + + FightBalancedBox + id: fightBalancedBox + margin-top: 22 + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + SafeFightBox + id: safeFightBox + margin-top: 22 + anchors.left: prev.right + anchors.top: parent.top + + FightDefensiveBox + id: fightDefensiveBox + margin-top: 44 + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + MountButton + id: mountButton + margin-top: 44 + anchors.left: prev.right + anchors.top: parent.top + + Panel + id: buttonsPanel + margin-top: 4 + margin-right: 5 + anchors.fill: parent + anchors.top: prev.bottom + layout: + type: verticalBox + + UIButton + id: buttonPvp + height: 20 + icon: /images/game/combatmodes/pvp + icon-clip: 0 0 42 20 + + $on: + icon-clip: 0 20 42 20 + + InventoryButton + !text: tr('Stop') + @onClick: g_game.stop(); g_game.cancelAttackAndFollow() + + InventoryButton + !text: tr('Options') + @onClick: modules.client_options.toggle() + + InventoryButton + !text: tr('Hotkeys') + @onClick: modules.game_hotkeys.toggle() + + InventoryButton + !text: tr('Logout') + @onClick: modules.game_interface.tryLogout() diff --git a/800OTClient/data/styles/40-minimap.otui b/800OTClient/data/styles/40-minimap.otui new file mode 100644 index 0000000..8a7a55e --- /dev/null +++ b/800OTClient/data/styles/40-minimap.otui @@ -0,0 +1,268 @@ +MinimapFlag < UIWidget + size: 11 11 + focusable: false + +MinimapCross < UIWidget + focusable: false + phantom: true + image: /images/game/minimap/cross + size: 16 16 + +MinimapFloorUpButton < Button + size: 20 20 + margin-right: 28 + margin-bottom: 28 + anchors.right: parent.right + anchors.bottom: parent.bottom + icon-source: /images/game/minimap/floor_up + icon-clip: 0 32 16 16 + $pressed: + icon-clip: 0 0 16 16 + $hover !pressed: + icon-clip: 0 16 16 16 + +MinimapFloorDownButton < Button + size: 20 20 + margin-right: 28 + margin-bottom: 4 + anchors.right: parent.right + anchors.bottom: parent.bottom + icon-source: /images/game/minimap/floor_down + icon-clip: 0 32 16 16 + $pressed: + icon-clip: 0 0 16 16 + $hover !pressed: + icon-clip: 0 16 16 16 + +MinimapZoomInButton < Button + text: + + size: 20 20 + margin-right: 4 + margin-bottom: 28 + anchors.right: parent.right + anchors.bottom: parent.bottom + //icon-source: /images/game/minimap/zoom_in + +MinimapZoomOutButton < Button + text: - + size: 20 20 + margin-right: 4 + margin-bottom: 4 + anchors.right: parent.right + anchors.bottom: parent.bottom + //icon-source: /images/game/minimap/zoom_out + +MinimapResetButton < Button + !text: tr('Center') + size: 44 20 + anchors.left: parent.left + anchors.top: parent.top + margin: 4 + +Minimap < UIMinimap + draggable: true + focusable: false + cross: true + color: black + + MinimapFloorUpButton + id: floorUpWidget + @onClick: self:getParent():floorUp(1) + + MinimapFloorDownButton + id: floorDownWidget + @onClick: self:getParent():floorDown(1) + + MinimapZoomInButton + id: zoomInWidget + @onClick: self:getParent():zoomIn() + + MinimapZoomOutButton + id: zoomOutWidget + @onClick: self:getParent():zoomOut() + + MinimapResetButton + id: resetWidget + @onClick: self:getParent():reset() + + +// Minimap Flag Create Window + + +MinimapFlagCheckBox < CheckBox + size: 15 15 + margin-left: 2 + image-source: /images/game/minimap/flagcheckbox + image-size: 15 15 + image-border: 3 + icon-source: /images/game/minimap/mapflags + icon-size: 11 11 + icon-offset: 2 4 + anchors.left: prev.right + anchors.top: prev.top + $!checked: + image-clip: 26 0 26 26 + $hover !checked: + image-clip: 78 0 26 26 + $checked: + image-clip: 0 0 26 26 + $hover checked: + image-clip: 52 0 26 26 + +MinimapFlagWindow < MainWindow + !text: tr('Create Map Mark') + size: 196 185 + + Label + !text: tr('Position') .. ':' + text-auto-resize: true + anchors.top: parent.top + anchors.left: parent.left + margin-top: 2 + + Label + id: position + text-auto-resize: true + anchors.top: parent.top + anchors.right: parent.right + margin-top: 2 + + Label + !text: tr('Description') .. ':' + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 7 + + TextEdit + id: description + margin-top: 3 + anchors.left: parent.left + anchors.top: prev.bottom + anchors.right: parent.right + + MinimapFlagCheckBox + id: flag0 + icon-source: /images/game/minimap/flag0 + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 6 + margin-left: 0 + + MinimapFlagCheckBox + id: flag1 + icon-source: /images/game/minimap/flag1 + + MinimapFlagCheckBox + id: flag2 + icon-source: /images/game/minimap/flag2 + + MinimapFlagCheckBox + id: flag3 + icon-source: /images/game/minimap/flag3 + + MinimapFlagCheckBox + id: flag4 + icon-source: /images/game/minimap/flag4 + + MinimapFlagCheckBox + id: flag5 + icon-source: /images/game/minimap/flag5 + + MinimapFlagCheckBox + id: flag6 + icon-source: /images/game/minimap/flag6 + + MinimapFlagCheckBox + id: flag7 + icon-source: /images/game/minimap/flag7 + + MinimapFlagCheckBox + id: flag8 + icon-source: /images/game/minimap/flag8 + + MinimapFlagCheckBox + id: flag9 + icon-source: /images/game/minimap/flag9 + + MinimapFlagCheckBox + id: flag10 + icon-source: /images/game/minimap/flag10 + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 6 + margin-left: 0 + + MinimapFlagCheckBox + id: flag11 + icon-source: /images/game/minimap/flag11 + + MinimapFlagCheckBox + id: flag12 + icon-source: /images/game/minimap/flag12 + + MinimapFlagCheckBox + id: flag13 + icon-source: /images/game/minimap/flag13 + + MinimapFlagCheckBox + id: flag14 + icon-source: /images/game/minimap/flag14 + + MinimapFlagCheckBox + id: flag15 + icon-source: /images/game/minimap/flag15 + + MinimapFlagCheckBox + id: flag16 + icon-source: /images/game/minimap/flag16 + + MinimapFlagCheckBox + id: flag17 + icon-source: /images/game/minimap/flag17 + + MinimapFlagCheckBox + id: flag18 + icon-source: /images/game/minimap/flag18 + + MinimapFlagCheckBox + id: flag19 + icon-source: /images/game/minimap/flag19 + + Button + id: okButton + !text: tr('Ok') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + + Button + id: cancelButton + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + +MinimapWindow < MiniWindow + height: 150 + + Label + text: ? + text-align: center + phantom: false + !tooltip: tr('Hold left mouse button to navigate\nScroll mouse middle button to zoom\nRight mouse button to create map marks\nPress Ctrl+Shift+M to view the entire game map') + anchors.top: lockButton.top + anchors.right: lockButton.left + margin-right: 3 + size: 14 14 + + MiniWindowContents + Minimap + id: minimap + anchors.fill: parent + + ResizeBorder + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + enabled: true \ No newline at end of file diff --git a/800OTClient/data/styles/40-outfitwindow.otui b/800OTClient/data/styles/40-outfitwindow.otui new file mode 100644 index 0000000..0e47d10 --- /dev/null +++ b/800OTClient/data/styles/40-outfitwindow.otui @@ -0,0 +1,476 @@ +PartCheckBoxes < Panel + height: 18 + + ButtonBox + id: head + font: cipsoftFont + !text: tr('Head') + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + checked: true + width: 62 + height: 18 + + ButtonBox + id: primary + font: cipsoftFont + !text: tr('Primary') + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + width: 62 + height: 18 + + ButtonBox + id: secondary + font: cipsoftFont + !text: tr('Secondary') + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + width: 62 + height: 18 + + ButtonBox + id: detail + font: cipsoftFont + !text: tr('Detail') + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + width: 62 + height: 18 + + ButtonBox + id: randomizeButton + font: cipsoftFont + !text: tr('Randomize') + !tooltip: tr('Randomize characters outfit') + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + width: 62 + height: 18 + @onClick: modules.game_outfit.randomize() + +AppearanceCategory < Panel + height: 20 + + $!first: + margin-top: 2 + + CheckBox + id: checkBox + image-source: /images/ui/checkbox_round + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + margin-left: 5 + width: 90 + text: Outfit: + @onClick: modules.game_outfit.onOptionChange(self:getParent():getId(), self:isChecked(), self) + + FlatLabel + id: description + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + anchors.right: parent.right + margin-right: 5 + text-align: center + text: - + +WindowPanel < Panel + image-source: /images/ui/window + image-border: 6 + image-border-top: 27 + padding: 5 + padding-top: 8 + +OptionsCheckBox < Panel + image-source: /images/ui/panel_flat + image-border: 1 + padding: 2 + padding-left: 7 + height: 22 + + CheckBox + id: check + anchors.centerIn: parent + anchors.left: parent.left + anchors.right: parent.right + text-align: left + @onCheckChange: modules.game_outfit.onOptionChange(self:getParent():getId(), self:isChecked(), self) + + $!first: + margin-top: 3 + +PreviewCreaturePanel < FlatPanel + padding: 3 + + Button + id: rotateLeft + anchors.left: parent.left + anchors.bottom: parent.bottom + size: 20 20 + text: < + @onClick: modules.game_outfit.rotatePreview(self:getId()) + + Button + id: rotateRight + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 20 20 + text: > + @onClick: modules.game_outfit.rotatePreview(self:getId()) + + UICreature + id: creature + size: 100 100 + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + +ConfigurePanel < WindowPanel + width: 150 + padding: 3 + + Label + id: title + margin-top: 5 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + font: verdana-11px-rounded + text: Configure + + Panel + id: options + anchors.fill: parent + anchors.top: prev.bottom + margin-top: 1 + padding: 5 + layout: verticalBox + +SmallPreviewTile < UICheckBox + padding: 5 + @onClick: modules.game_outfit.onElementSelect(self) + image-source: /images/ui/button + image-color: #dfdfdf + image-clip: 0 0 22 23 + image-border: 10 + opacity: 1.0 + + $pressed: + image-clip: 0 46 22 23 + + $hover: + opacity: 0.75 + + UICreature + id: creature + size: 60 60 + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + phantom: true + creature-fixed-size: true + + UIWidget + id: item + anchors.fill: prev + phantom: true + + Label + id: title + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + text-align: center + font: verdana-11px-rounded + + $checked: + border-width: 1 + border-color: #ffffff + + $!checked: + border-width: 0 + +LargePreviewTile < UICheckBox + padding: 15 15 2 15 + @onClick: modules.game_outfit.onElementSelect(self) + image-source: /images/ui/button + image-color: #dfdfdf + image-clip: 0 0 22 23 + image-border: 10 + opacity: 1.0 + + $pressed: + image-clip: 0 46 22 23 + + UICreature + id: outfit + size: 60 60 + anchors.left: parent.left + margin-left: 10 + anchors.top: parent.top + phantom: true + + UICreature + id: mount + size: 60 60 + anchors.right: parent.right + margin-right: 10 + anchors.top: parent.top + phantom: true + + Label + id: title + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + text-align: center + font: verdana-11px-rounded + + $checked: + border-width: 1 + border-color: #ffffff + + $!checked: + border-width: 0 + +FilterPanel < WindowPanel + size: 242 56 + padding-left: 10 + padding-right: 10 + padding-bottom: 8 + + Label + id: title + anchors.left: parent.left + anchors.right: parent.right + text-align: center + anchors.top: parent.top + font: verdana-11px-rounded + text: Filter outfits + + Button + id: clear + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 20 20 + text: X + @onClick: modules.game_outfit.clearFilterText() + + TextEdit + id: filterWindow + anchors.right: prev.left + anchors.left: parent.left + anchors.bottom: parent.bottom + height: 20 + placeholder: Type to search + placehoder-color: black + @onTextChange: modules.game_outfit.onFilterList(self:getText()) + +PresetPanel < WindowPanel + size: 242 47 + padding-left: 2 + padding-bottom: 2 + + Label + id: title + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + font: verdana-11px-rounded + text: Manage Presets + + Button + id: new + size: 45 18 + font: cipsoftFont + text: New + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-bottom: 6 + margin-left: 25 + @onClick: modules.game_outfit.onPresetButtonPress(self:getId()) + + Button + id: rename + size: 45 18 + font: cipsoftFont + text: Rename + anchors.left: prev.right + margin-left: 3 + anchors.verticalCenter: prev.verticalCenter + @onClick: modules.game_outfit.onPresetButtonPress(self:getId()) + + + Button + id: save + size: 45 18 + font: cipsoftFont + text: Save + anchors.left: prev.right + margin-left: 3 + anchors.verticalCenter: prev.verticalCenter + @onClick: modules.game_outfit.onPresetButtonPress(self:getId()) + + Button + id: delete + size: 45 18 + font: cipsoftFont + text: Delete + anchors.left: prev.right + margin-left: 3 + anchors.verticalCenter: prev.verticalCenter + @onClick: modules.game_outfit.onPresetButtonPress(self:getId()) + +PreviewPanel < WindowPanel + size: 477 205 + + Label + id: title + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + font: verdana-11px-rounded + text: Preview Selection + + FlatPanel + id: options + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + margin: 3 + margin-top: 19 + padding: 5 + width: 120 + layout: verticalBox + + Label + font: verdana-11px-rounded + text-align: center + text: Show: + + PreviewCreaturePanel + id: creaturePanel + margin: 3 + margin-top: 19 + anchors.fill: parent + anchors.left: prev.right + +AppearancePanel < WindowPanel + layout: + type: verticalBox + fit-children: true + + Panel + id: categories + margin-top: 20 + layout: + type: verticalBox + fit-children: true + + PartCheckBoxes + id: parts + margin-top: 3 + margin-left: 7 + + Panel + id: colorBoxPanel + padding: 2 2 2 5 + layout: + type: grid + cell-size: 14 14 + cell-spacing: 2 + num-columns: 19 + num-lines: 7 + fit-children: true + +ListBox < ScrollableFlatPanel + width: 242 + padding-top: 6 + padding-left: 6 + padding-bottom: 6 + layout: + type: grid + num-columns: 2 + cell-size: 106 100 + cell-spacing: 6 + flow: true + +OutfitWindow < MainWindow + size: 760 519 + padding-top: 35 + !text: tr('Customize Character') + + FilterPanel + id: search + anchors.top: parent.top + anchors.right: parent.right + + PresetPanel + id: preset + anchors.fill: prev + visible: false + + ListBox + id: list + anchors.top: prev.bottom + margin-top: 5 + anchors.right: parent.right + anchors.bottom: bottomSep.top + margin-bottom: 5 + vertical-scrollbar: scrollBar + + VerticalScrollBar + id: scrollBar + anchors.top: list.top + anchors.bottom: list.bottom + anchors.right: list.right + step: 14 + pixels-scroll: true + + PreviewPanel + id: preview + anchors.top: parent.top + anchors.left: parent.left + + ConfigurePanel + id: config + anchors.top: prev.bottom + margin-top: 5 + anchors.bottom: bottomSep.top + margin-bottom: 5 + anchors.left: parent.left + + AppearancePanel + id: appearance + anchors.left: prev.right + anchors.top: preview.bottom + anchors.right: preview.right + margin: 5 0 5 5 + + Label + anchors.top: prev.top + margin-top: 8 + anchors.horizontalCenter: prev.horizontalCenter + font: verdana-11px-rounded + text: Change Appearance + + HorizontalSeparator + id: bottomSep + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: closeButton.top + margin-bottom: 8 + + Button + id: closeButton + !text: tr('Cancel') + font: cipsoftFont + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 45 21 + @onClick: modules.game_outfit.destroy() + + Button + id: okButton + !text: tr('Ok') + font: cipsoftFont + anchors.right: prev.left + margin-right: 7 + anchors.bottom: parent.bottom + size: 45 21 + @onClick: modules.game_outfit.accept() \ No newline at end of file diff --git a/800OTClient/data/styles/40-tilewidget.otui b/800OTClient/data/styles/40-tilewidget.otui new file mode 100644 index 0000000..0f96ee8 --- /dev/null +++ b/800OTClient/data/styles/40-tilewidget.otui @@ -0,0 +1,2 @@ +TileWidget < UIWidget + auto-draw: false diff --git a/800OTClient/data/things/772/Tibia.dat b/800OTClient/data/things/772/Tibia.dat new file mode 100644 index 0000000..9afa4b1 Binary files /dev/null and b/800OTClient/data/things/772/Tibia.dat differ diff --git a/800OTClient/data/things/772/Tibia.otfi b/800OTClient/data/things/772/Tibia.otfi new file mode 100644 index 0000000..62ea96c --- /dev/null +++ b/800OTClient/data/things/772/Tibia.otfi @@ -0,0 +1,6 @@ +DatSpr + extended: false + transparency: false + frame-durations: false + frame-groups: false + assets-name: Tibia \ No newline at end of file diff --git a/800OTClient/data/things/772/Tibia.spr b/800OTClient/data/things/772/Tibia.spr new file mode 100644 index 0000000..de15dc1 Binary files /dev/null and b/800OTClient/data/things/772/Tibia.spr differ diff --git a/800OTClient/data/things/800/Tibia.dat b/800OTClient/data/things/800/Tibia.dat new file mode 100644 index 0000000..2e38b13 Binary files /dev/null and b/800OTClient/data/things/800/Tibia.dat differ diff --git a/800OTClient/data/things/800/Tibia.spr b/800OTClient/data/things/800/Tibia.spr new file mode 100644 index 0000000..84b8e5f Binary files /dev/null and b/800OTClient/data/things/800/Tibia.spr differ diff --git a/800OTClient/init.lua b/800OTClient/init.lua new file mode 100644 index 0000000..e2abdc6 --- /dev/null +++ b/800OTClient/init.lua @@ -0,0 +1,92 @@ +-- CONFIG +APP_NAME = "otclientv8" -- important, change it, it's name for config dir and files in appdata +APP_VERSION = 1341 -- client version for updater and login to identify outdated client +DEFAULT_LAYOUT = "retro" -- on android it's forced to "mobile", check code bellow + +-- If you don't use updater or other service, set it to updater = "" +Services = { + website = "http://otclient.ovh", -- currently not used + updater = "http://otclient.ovh/api/updater.php", + stats = "", + crash = "http://otclient.ovh/api/crash.php", + feedback = "http://otclient.ovh/api/feedback.php", + status = "http://otclient.ovh/api/status.php" +} + +-- Servers accept http login url, websocket login url or ip:port:version +Servers = { +--[[ OTClientV8 = "http://otclient.ovh/api/login.php", + OTClientV8proxy = "http://otclient.ovh/api/login.php?proxy=1", + OTClientV8c = "otclient.ovh:7171:1099:25:30:80:90", + OTClientV8Test = "http://otclient.ovh/api/login2.php", + Evoulinia = "evolunia.net:7171:1098", + GarneraTest = "garnera-global.net:7171:1100", + LocalTestServ = "127.0.0.1:7171:1098:110:30:93" ]] +} + +--Server = "ws://otclient.ovh:3000/" +--Server = "ws://127.0.0.1:88/" +--USE_NEW_ENERGAME = true -- uses entergamev2 based on websockets instead of entergame +ALLOW_CUSTOM_SERVERS = true -- if true it shows option ANOTHER on server list + +g_app.setName("OTCv8") +-- CONFIG END + +-- print first terminal message +g_logger.info(os.date("== application started at %b %d %Y %X")) +g_logger.info(g_app.getName() .. ' ' .. g_app.getVersion() .. ' rev ' .. g_app.getBuildRevision() .. ' (' .. g_app.getBuildCommit() .. ') made by ' .. g_app.getAuthor() .. ' built on ' .. g_app.getBuildDate() .. ' for arch ' .. g_app.getBuildArch()) + +if not g_resources.directoryExists("/data") then + g_logger.fatal("Data dir doesn't exist.") +end + +if not g_resources.directoryExists("/modules") then + g_logger.fatal("Modules dir doesn't exist.") +end + +-- settings +g_configs.loadSettings("/config.otml") + +-- set layout +local settings = g_configs.getSettings() +local layout = DEFAULT_LAYOUT +if g_app.isMobile() then + layout = "mobile" +elseif settings:exists('layout') then + layout = settings:getValue('layout') +end +g_resources.setLayout(layout) + +-- load mods +g_modules.discoverModules() +g_modules.ensureModuleLoaded("corelib") + +local function loadModules() + -- libraries modules 0-99 + g_modules.autoLoadModules(99) + g_modules.ensureModuleLoaded("gamelib") + + -- client modules 100-499 + g_modules.autoLoadModules(499) + g_modules.ensureModuleLoaded("client") + + -- game modules 500-999 + g_modules.autoLoadModules(999) + g_modules.ensureModuleLoaded("game_interface") + + -- mods 1000-9999 + g_modules.autoLoadModules(9999) +end + +-- report crash +if type(Services.crash) == 'string' and Services.crash:len() > 4 and g_modules.getModule("crash_reporter") then + g_modules.ensureModuleLoaded("crash_reporter") +end + +-- run updater, must use data.zip +if type(Services.updater) == 'string' and Services.updater:len() > 4 + and g_resources.isLoadedFromArchive() and g_modules.getModule("updater") then + g_modules.ensureModuleLoaded("updater") + return Updater.init(loadModules) +end +loadModules() diff --git a/800OTClient/layouts/README.md b/800OTClient/layouts/README.md new file mode 100644 index 0000000..4cbc6ea --- /dev/null +++ b/800OTClient/layouts/README.md @@ -0,0 +1,4 @@ +## Layouts overwrite files from `/data` +Foe example, if you have file `/data/images/background.png` and `/layouts/dragonball/images/background.png`, and dragonball layout is selected, then `/layouts/dragonball/images/background.png` will be loaded instead of `/data/images/background.png`. + +## Dont make layout named `default`, this name is reserved diff --git a/800OTClient/layouts/mobile/README.md b/800OTClient/layouts/mobile/README.md new file mode 100644 index 0000000..b0367ed --- /dev/null +++ b/800OTClient/layouts/mobile/README.md @@ -0,0 +1 @@ +Min. height for mobile is 360, don't make windows bigger than that \ No newline at end of file diff --git a/800OTClient/layouts/mobile/styles/10-scrollbars.otui b/800OTClient/layouts/mobile/styles/10-scrollbars.otui new file mode 100644 index 0000000..63a0ebf --- /dev/null +++ b/800OTClient/layouts/mobile/styles/10-scrollbars.otui @@ -0,0 +1,108 @@ +ScrollBarSlider < UIButton + id: sliderButton + anchors.centerIn: parent + size: 16 20 + image-source: /images/ui/scrollbar + image-clip: 0 26 13 13 + image-border: 2 + image-color: #ffffffff + $hover: + image-clip: 13 26 13 13 + $pressed: + image-clip: 26 26 13 13 + $disabled: + image-color: #ffffff66 + +ScrollBarValueLabel < Label + id: valueLabel + anchors.fill: parent + color: white + text-align: center + +VerticalScrollBar < UIScrollBar + orientation: vertical + width: 16 + height: 39 + image-source: /images/ui/scrollbar + image-clip: 39 0 13 65 + image-border: 1 + pixels-scroll: true + + UIButton + id: decrementButton + anchors.top: parent.top + anchors.left: parent.left + image-source: /images/ui/scrollbar + image-clip: 0 0 13 13 + image-color: #ffffffff + size: 16 16 + $hover: + image-clip: 13 0 13 13 + $pressed: + image-clip: 26 0 13 13 + $disabled: + image-color: #ffffff66 + + UIButton + id: incrementButton + anchors.bottom: parent.bottom + anchors.right: parent.right + size: 16 16 + image-source: /images/ui/scrollbar + image-clip: 0 13 13 13 + image-color: #ffffffff + $hover: + image-clip: 13 13 13 13 + $pressed: + image-clip: 26 13 13 13 + $disabled: + image-color: #ffffff66 + + ScrollBarSlider + + ScrollBarValueLabel + +HorizontalScrollBar < UIScrollBar + orientation: horizontal + height: 16 + width: 39 + image-source: /images/ui/scrollbar + image-clip: 0 65 52 13 + image-border: 1 + + $disabled: + color: #bbbbbb88 + + UIButton + id: decrementButton + anchors.top: parent.top + anchors.left: parent.left + image-source: /images/ui/scrollbar + image-clip: 0 39 13 13 + image-color: #ffffffff + size: 16 16 + $hover: + image-clip: 13 39 13 13 + $pressed: + image-clip: 26 39 13 13 + $disabled: + image-color: #ffffff66 + + UIButton + id: incrementButton + anchors.bottom: parent.bottom + anchors.right: parent.right + size: 16 16 + image-source: /images/ui/scrollbar + image-clip: 0 52 13 13 + image-color: #ffffffff + $hover: + image-clip: 13 52 13 13 + $pressed: + image-clip: 26 52 13 13 + $disabled: + image-color: #ffffff66 + + ScrollBarSlider + + ScrollBarValueLabel diff --git a/800OTClient/layouts/mobile/styles/20-smallscrollbar.otui b/800OTClient/layouts/mobile/styles/20-smallscrollbar.otui new file mode 100644 index 0000000..e498c0f --- /dev/null +++ b/800OTClient/layouts/mobile/styles/20-smallscrollbar.otui @@ -0,0 +1,60 @@ +SmallScrollBar < UIScrollBar + orientation: vertical + margin-bottom: 1 + step: 20 + width: 16 + image-source: /images/ui/scrollbar + image-clip: 39 0 13 65 + image-border: 1 + pixels-scroll: true + + UIButton + id: decrementButton + anchors.top: parent.top + anchors.left: parent.left + image-source: /images/ui/scrollbar + image-clip: 0 0 13 13 + image-color: #ffffffff + size: 16 16 + $hover: + image-clip: 13 0 13 13 + $pressed: + image-clip: 26 0 13 13 + $disabled: + image-color: #ffffff66 + + UIButton + id: incrementButton + anchors.bottom: parent.bottom + anchors.right: parent.right + size: 16 16 + image-source: /images/ui/scrollbar + image-clip: 0 13 13 13 + image-color: #ffffffff + $hover: + image-clip: 13 13 13 13 + $pressed: + image-clip: 26 13 13 13 + $disabled: + image-color: #ffffff66 + + UIButton + id: sliderButton + anchors.centerIn: parent + size: 16 20 + image-source: /images/ui/scrollbar + image-clip: 0 26 13 13 + image-border: 2 + image-color: #ffffffff + $hover: + image-clip: 13 26 13 13 + $pressed: + image-clip: 26 26 13 13 + $disabled: + image-color: #ffffff66 + + Label + id: valueLabel + anchors.fill: parent + color: white + text-align: center \ No newline at end of file diff --git a/800OTClient/layouts/mobile/styles/30-miniwindow.otui b/800OTClient/layouts/mobile/styles/30-miniwindow.otui new file mode 100644 index 0000000..41ad5b0 --- /dev/null +++ b/800OTClient/layouts/mobile/styles/30-miniwindow.otui @@ -0,0 +1,128 @@ +MiniWindow < UIMiniWindow + font: verdana-11px-antialised + icon-rect: 4 4 16 16 + width: 192 + height: 200 + text-offset: 24 5 + text-align: topLeft + image-source: /images/ui/miniwindow + image-border: 4 + image-border-top: 23 + image-border-bottom: 4 + focusable: false + &minimizedHeight: 24 + + $on: + image-border-bottom: 2 + + UIWidget + id: miniwindowTopBar + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + margin-right: 3 + margin-left: 3 + margin-top: 3 + size: 258 18 + phantom: true + + UIButton + id: closeButton + anchors.top: parent.top + anchors.right: parent.right + margin-top: 5 + margin-right: 5 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 28 0 14 14 + + $hover: + image-clip: 28 14 14 14 + + $pressed: + image-clip: 28 28 14 14 + + UIButton + id: minimizeButton + anchors.top: closeButton.top + anchors.right: closeButton.left + margin-right: 3 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 0 0 14 14 + + $hover: + image-clip: 0 14 14 14 + + $pressed: + image-clip: 0 28 14 14 + + $on: + image-clip: 14 0 14 14 + + $on hover: + image-clip: 14 14 14 14 + + $on pressed: + image-clip: 14 28 14 14 + + UIButton + id: lockButton + anchors.top: minimizeButton.top + anchors.right: minimizeButton.left + margin-right: 3 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 112 0 14 14 + + $hover: + image-clip: 112 14 14 14 + + $pressed: + image-clip: 112 28 14 14 + + $on: + image-clip: 98 0 14 14 + + $on hover: + image-clip: 98 14 14 14 + + $on pressed: + image-clip: 98 28 14 14 + + VerticalScrollBar + id: miniwindowScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 14 + margin-top: 22 + margin-right: 3 + margin-bottom: 3 + pixels-scroll: true + + $!on: + width: 0 + + ResizeBorder + id: bottomResizeBorder + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 8 + minimum: 48 + margin-left: 3 + margin-right: 3 + background: #ffffff88 + +MiniWindowContents < ScrollablePanel + id: contentsPanel + anchors.fill: parent + anchors.right: miniwindowScrollBar.left + margin-left: 3 + margin-bottom: 8 + margin-top: 22 + margin-right: 1 + vertical-scrollbar: miniwindowScrollBar + +HeadlessMiniWindow < MiniWindow diff --git a/800OTClient/layouts/mobile/styles/40-console.otui b/800OTClient/layouts/mobile/styles/40-console.otui new file mode 100644 index 0000000..25f9730 --- /dev/null +++ b/800OTClient/layouts/mobile/styles/40-console.otui @@ -0,0 +1,165 @@ +ConsoleLabel < UITextEdit + font: verdana-11px-antialised + height: 14 + color: yellow + margin-left: 2 + text-wrap: true + text-auto-resize: true + selection-color: #111416 + selection-background-color: #999999 + change-cursor-image: false + cursor-visible: false + editable: false + draggable: true + selectable: false + focusable: false + +ConsolePhantomLabel < UILabel + font: verdana-11px-antialised + height: 14 + color: yellow + text-wrap: true + text-auto-resize: true + selection-color: #111416 + selection-background-color: #999999 + +ConsoleTabBar < MoveableTabBar + height: 22 + +ConsoleTabBarPanel < MoveableTabBarPanel + id: consoleTab + + ScrollablePanel + id: consoleBuffer + anchors.fill: parent + margin-right: 12 + vertical-scrollbar: consoleScrollBar + layout: + type: verticalBox + align-bottom: true + border-width: 1 + border-color: #202327 + background: #00000066 + inverted-scroll: true + padding: 1 + + VerticalScrollBar + id: consoleScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 14 + pixels-scroll: true + +ConsoleTabBarButton < MoveableTabBarButton + height: 22 + padding: 5 + +ConsolePanel < Panel + image-source: /images/ui/panel_bottom + image-border: 4 + + $first: + anchors.fill: parent + + $!first: + anchors.top: prev.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + + CheckBox + id: toggleChat + !tooltip: tr('Disable chat mode, allow to walk using ASDW') + anchors.left: parent.left + anchors.top: parent.top + @onCheckChange: toggleChat() + visible: false + + TabButton + id: prevChannelButton + icon: /images/game/console/leftarrow + anchors.left: parent.left + anchors.top: parent.top + + ConsoleTabBar + id: consoleTabBar + anchors.left: prev.right + anchors.top: parent.top + anchors.right: next.left + tab-spacing: 2 + movable: true + + TabButton + id: nextChannelButton + icon: /images/game/console/rightarrow + anchors.right: next.left + anchors.top: parent.top + + TabButton + id: closeChannelButton + !tooltip: tr('Close this channel') .. ' (Ctrl+E)' + icon: /images/game/console/closechannel + anchors.right: next.left + anchors.top: parent.top + enabled: false + @onClick: removeCurrentTab() + + TabButton + id: clearChannelButton + !tooltip: tr('Clear current message window') + icon: /images/game/console/clearchannel + anchors.right: next.left + anchors.top: parent.top + @onClick: | + local consoleTabBar = self:getParent():getChildById('consoleTabBar') + clearChannel(consoleTabBar) + + TabButton + id: channelsButton + !tooltip: tr('Open new channel') .. ' (Ctrl+O)' + icon: /images/game/console/channels + anchors.right: next.left + anchors.top: parent.top + @onClick: g_game.requestChannels() + + TabButton + id: ignoreButton + !tooltip: tr('Ignore players') + icon: /images/game/console/ignore + anchors.right: parent.right + anchors.top: parent.top + @onClick: onClickIgnoreButton() + + Panel + id: consoleContentPanel + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: consoleTextEdit.top + padding: 1 + focusable: false + phantom: true + + TabButton + id: sayModeButton + icon: /images/game/console/say + !tooltip: tr('Adjust volume') + &sayMode: 2 + size: 22 22 + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-left: 6 + @onClick: sayModeChange() + + TextEdit + id: consoleTextEdit + anchors.left: sayModeButton.right + anchors.right: parent.right + anchors.bottom: parent.bottom + height: 22 + margin-right: 6 + margin-left: 6 + shift-navigation: true + max-length: 255 + text-auto-submit: true diff --git a/800OTClient/layouts/mobile/styles/40-inventory.otui b/800OTClient/layouts/mobile/styles/40-inventory.otui new file mode 100644 index 0000000..d414065 --- /dev/null +++ b/800OTClient/layouts/mobile/styles/40-inventory.otui @@ -0,0 +1,299 @@ +InventoryItem < Item + $on: + image-source: /images/ui/item-blessed + +HeadSlot < InventoryItem + id: slot1 + image-source: /images/game/slots/head + &position: {x=65535, y=1, z=0} + $on: + image-source: /images/game/slots/head-blessed + +BodySlot < InventoryItem + id: slot4 + image-source: /images/game/slots/body + &position: {x=65535, y=4, z=0} + $on: + image-source: /images/game/slots/body-blessed + +LegSlot < InventoryItem + id: slot7 + image-source: /images/game/slots/legs + &position: {x=65535, y=7, z=0} + $on: + image-source: /images/game/slots/legs-blessed + +FeetSlot < InventoryItem + id: slot8 + image-source: /images/game/slots/feet + &position: {x=65535, y=8, z=0} + $on: + image-source: /images/game/slots/feet-blessed + +NeckSlot < InventoryItem + id: slot2 + image-source: /images/game/slots/neck + &position: {x=65535, y=2, z=0} + $on: + image-source: /images/game/slots/neck-blessed + +LeftSlot < InventoryItem + id: slot6 + image-source: /images/game/slots/left-hand + &position: {x=65535, y=6, z=0} + $on: + image-source: /images/game/slots/left-hand-blessed + +FingerSlot < InventoryItem + id: slot9 + image-source: /images/game/slots/finger + &position: {x=65535, y=9, z=0} + $on: + image-source: /images/game/slots/finger-blessed + +BackSlot < InventoryItem + id: slot3 + image-source: /images/game/slots/back + &position: {x=65535, y=3, z=0} + $on: + image-source: /images/game/slots/back-blessed + +RightSlot < InventoryItem + id: slot5 + image-source: /images/game/slots/right-hand + &position: {x=65535, y=5, z=0} + $on: + image-source: /images/game/slots/right-hand-blessed + +AmmoSlot < InventoryItem + id: slot10 + image-source: /images/game/slots/ammo + &position: {x=65535, y=10, z=0} + $on: + image-source: /images/game/slots/ammo-blessed + +PurseButton < UIButton + id: purseButton + size: 34 12 + !tooltip: tr('Open purse') + icon-source: /images/game/slots/purse + icon-clip: 0 0 34 12 + + $on: + icon-clip: 0 12 34 12 + + $pressed: + icon-clip: 0 12 34 12 + +CombatBox < UICheckBox + size: 20 20 + image-clip: 0 0 20 20 + margin-left: 4 + + $checked: + image-clip: 0 20 20 20 + + +InventoryButton < Button + font: verdana-11px-antialised + height: 18 + margin-top: 2 + text-align: center + +SoulCapLabel < GameLabel + text-align: center + color: #FFFFFF + font: cipsoftFont + margin-top: 4 + text-offset: 0 3 + width: 36 + height: 20 + icon-source: /images/game/slots/soulcap + +FightOffensiveBox < CombatBox + image-source: /images/game/combatmodes/fightoffensive +FightBalancedBox < CombatBox + image-source: /images/game/combatmodes/fightbalanced +FightDefensiveBox < CombatBox + image-source: /images/game/combatmodes/fightdefensive +ChaseModeBox < CombatBox + image-source: /images/game/combatmodes/chasemode +SafeFightBox < CombatBox + image-source: /images/game/combatmodes/safefight + +MountButton < CombatBox + image-source: /images/game/combatmodes/mount + +InventoryWindow < MiniWindow + icon: /images/topbuttons/inventory + height: 200 + id: inventoryWindow + @onClose: modules.game_inventory.onMiniWindowClose() + &save: true + &autoOpen: 3 + + MiniWindowContents + anchors.left: parent.left + + Panel + id: inventoryPanel + margin-right: 63 + margin-top: 2 + anchors.fill: parent + + HeadSlot + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 3 + + BodySlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + LegSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + FeetSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + NeckSlot + anchors.top: slot1.top + anchors.right: slot1.left + margin-top: 13 + margin-right: 5 + + LeftSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + FingerSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + BackSlot + anchors.top: slot1.top + anchors.left: slot1.right + margin-top: 13 + margin-left: 5 + + RightSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + AmmoSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + SoulCapLabel + id: soulLabel + anchors.top: slot10.bottom + anchors.horizontalCenter: slot10.horizontalCenter + + SoulCapLabel + id: capLabel + anchors.top: slot9.bottom + anchors.horizontalCenter: slot9.horizontalCenter + + PurseButton + anchors.left: slot3.left + anchors.bottom: slot3.top + margin-bottom: 3 + + Panel + id: conditionPanel + layout: + type: horizontalBox + height: 22 + padding: 2 + anchors.top: slot8.bottom + anchors.left: slot6.left + anchors.right: slot5.right + margin-top: 4 + border-width: 1 + border-color: #00000077 + background-color: #ffffff22 + + Panel + margin-top: 5 + anchors.fill: parent + anchors.left: prev.right + + FightOffensiveBox + id: fightOffensiveBox + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + ChaseModeBox + id: chaseModeBox + anchors.left: prev.right + anchors.top: parent.top + + FightBalancedBox + id: fightBalancedBox + margin-top: 22 + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + SafeFightBox + id: safeFightBox + margin-top: 22 + anchors.left: prev.right + anchors.top: parent.top + + FightDefensiveBox + id: fightDefensiveBox + margin-top: 44 + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + MountButton + id: mountButton + margin-top: 44 + anchors.left: prev.right + anchors.top: parent.top + + Panel + id: buttonsPanel + margin-top: 4 + margin-right: 5 + anchors.fill: parent + anchors.top: prev.bottom + layout: + type: verticalBox + + UIButton + id: buttonPvp + height: 20 + icon: /images/game/combatmodes/pvp + icon-clip: 0 0 42 20 + + $on: + icon-clip: 0 20 42 20 + + InventoryButton + !text: tr('Stop') + @onClick: g_game.stop(); g_game.cancelAttackAndFollow() + + InventoryButton + !text: tr('Options') + @onClick: modules.client_options.toggle() + + InventoryButton + !text: tr('Quests') + @onClick: g_game.requestQuestLog() + + InventoryButton + !text: tr('Logout') + @onClick: modules.game_interface.tryLogout() diff --git a/800OTClient/layouts/retro/images/background.png b/800OTClient/layouts/retro/images/background.png new file mode 100644 index 0000000..daa666e Binary files /dev/null and b/800OTClient/layouts/retro/images/background.png differ diff --git a/800OTClient/layouts/retro/images/game/console/channels.png b/800OTClient/layouts/retro/images/game/console/channels.png new file mode 100644 index 0000000..017d2eb Binary files /dev/null and b/800OTClient/layouts/retro/images/game/console/channels.png differ diff --git a/800OTClient/layouts/retro/images/game/console/clearchannel.png b/800OTClient/layouts/retro/images/game/console/clearchannel.png new file mode 100644 index 0000000..201bd82 Binary files /dev/null and b/800OTClient/layouts/retro/images/game/console/clearchannel.png differ diff --git a/800OTClient/layouts/retro/images/game/console/closechannel.png b/800OTClient/layouts/retro/images/game/console/closechannel.png new file mode 100644 index 0000000..46ea537 Binary files /dev/null and b/800OTClient/layouts/retro/images/game/console/closechannel.png differ diff --git a/800OTClient/layouts/retro/images/game/console/ignore.png b/800OTClient/layouts/retro/images/game/console/ignore.png new file mode 100644 index 0000000..ab08dc1 Binary files /dev/null and b/800OTClient/layouts/retro/images/game/console/ignore.png differ diff --git a/800OTClient/layouts/retro/images/game/console/leftarrow.png b/800OTClient/layouts/retro/images/game/console/leftarrow.png new file mode 100644 index 0000000..7e065f5 Binary files /dev/null and b/800OTClient/layouts/retro/images/game/console/leftarrow.png differ diff --git a/800OTClient/layouts/retro/images/game/console/rightarrow.png b/800OTClient/layouts/retro/images/game/console/rightarrow.png new file mode 100644 index 0000000..4c51e9f Binary files /dev/null and b/800OTClient/layouts/retro/images/game/console/rightarrow.png differ diff --git a/800OTClient/layouts/retro/images/game/console/say.png b/800OTClient/layouts/retro/images/game/console/say.png new file mode 100644 index 0000000..7f28bae Binary files /dev/null and b/800OTClient/layouts/retro/images/game/console/say.png differ diff --git a/800OTClient/layouts/retro/images/game/console/trademsg.png b/800OTClient/layouts/retro/images/game/console/trademsg.png new file mode 100644 index 0000000..9d96ffb Binary files /dev/null and b/800OTClient/layouts/retro/images/game/console/trademsg.png differ diff --git a/800OTClient/layouts/retro/images/game/console/whisper.png b/800OTClient/layouts/retro/images/game/console/whisper.png new file mode 100644 index 0000000..a66f48f Binary files /dev/null and b/800OTClient/layouts/retro/images/game/console/whisper.png differ diff --git a/800OTClient/layouts/retro/images/game/console/yell.png b/800OTClient/layouts/retro/images/game/console/yell.png new file mode 100644 index 0000000..3fedf22 Binary files /dev/null and b/800OTClient/layouts/retro/images/game/console/yell.png differ diff --git a/800OTClient/layouts/retro/images/game/prey/prey_damage.png b/800OTClient/layouts/retro/images/game/prey/prey_damage.png new file mode 100644 index 0000000..e36763b Binary files /dev/null and b/800OTClient/layouts/retro/images/game/prey/prey_damage.png differ diff --git a/800OTClient/layouts/retro/images/game/prey/prey_defense.png b/800OTClient/layouts/retro/images/game/prey/prey_defense.png new file mode 100644 index 0000000..824af0c Binary files /dev/null and b/800OTClient/layouts/retro/images/game/prey/prey_defense.png differ diff --git a/800OTClient/layouts/retro/images/game/prey/prey_inactive.png b/800OTClient/layouts/retro/images/game/prey/prey_inactive.png new file mode 100644 index 0000000..0df3385 Binary files /dev/null and b/800OTClient/layouts/retro/images/game/prey/prey_inactive.png differ diff --git a/800OTClient/layouts/retro/images/game/prey/prey_loot.png b/800OTClient/layouts/retro/images/game/prey/prey_loot.png new file mode 100644 index 0000000..8bda60c Binary files /dev/null and b/800OTClient/layouts/retro/images/game/prey/prey_loot.png differ diff --git a/800OTClient/layouts/retro/images/game/prey/prey_no_bonus.png b/800OTClient/layouts/retro/images/game/prey/prey_no_bonus.png new file mode 100644 index 0000000..db783ed Binary files /dev/null and b/800OTClient/layouts/retro/images/game/prey/prey_no_bonus.png differ diff --git a/800OTClient/layouts/retro/images/game/prey/prey_xp.png b/800OTClient/layouts/retro/images/game/prey/prey_xp.png new file mode 100644 index 0000000..da76fca Binary files /dev/null and b/800OTClient/layouts/retro/images/game/prey/prey_xp.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/analyzers.png b/800OTClient/layouts/retro/images/topbuttons/analyzers.png new file mode 100644 index 0000000..db2fa61 Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/analyzers.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/audio.png b/800OTClient/layouts/retro/images/topbuttons/audio.png new file mode 100644 index 0000000..d4c6dc6 Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/audio.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/audio_mute.png b/800OTClient/layouts/retro/images/topbuttons/audio_mute.png new file mode 100644 index 0000000..33863ad Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/audio_mute.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/battle.png b/800OTClient/layouts/retro/images/topbuttons/battle.png new file mode 100644 index 0000000..939baba Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/battle.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/bot.png b/800OTClient/layouts/retro/images/topbuttons/bot.png new file mode 100644 index 0000000..508b2b7 Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/bot.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/ciclopedia.png b/800OTClient/layouts/retro/images/topbuttons/ciclopedia.png new file mode 100644 index 0000000..8ecd0bf Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/ciclopedia.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/compedium.png b/800OTClient/layouts/retro/images/topbuttons/compedium.png new file mode 100644 index 0000000..6fe888f Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/compedium.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/cooldowns.png b/800OTClient/layouts/retro/images/topbuttons/cooldowns.png new file mode 100644 index 0000000..fd5d845 Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/cooldowns.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/dailyreward.png b/800OTClient/layouts/retro/images/topbuttons/dailyreward.png new file mode 100644 index 0000000..1aa046a Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/dailyreward.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/debug.png b/800OTClient/layouts/retro/images/topbuttons/debug.png new file mode 100644 index 0000000..a04c2fe Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/debug.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/exit.png b/800OTClient/layouts/retro/images/topbuttons/exit.png new file mode 100644 index 0000000..15f4b71 Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/exit.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/healthinfo.png b/800OTClient/layouts/retro/images/topbuttons/healthinfo.png new file mode 100644 index 0000000..68ac924 Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/healthinfo.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/hotkeys.png b/800OTClient/layouts/retro/images/topbuttons/hotkeys.png new file mode 100644 index 0000000..bbb9901 Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/hotkeys.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/inventory.png b/800OTClient/layouts/retro/images/topbuttons/inventory.png new file mode 100644 index 0000000..f5a3479 Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/inventory.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/login.png b/800OTClient/layouts/retro/images/topbuttons/login.png new file mode 100644 index 0000000..3019fae Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/login.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/logout.png b/800OTClient/layouts/retro/images/topbuttons/logout.png new file mode 100644 index 0000000..4813b91 Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/logout.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/minimap.png b/800OTClient/layouts/retro/images/topbuttons/minimap.png new file mode 100644 index 0000000..c397923 Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/minimap.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/modulemanager.png b/800OTClient/layouts/retro/images/topbuttons/modulemanager.png new file mode 100644 index 0000000..2a5fc48 Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/modulemanager.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/motd.png b/800OTClient/layouts/retro/images/topbuttons/motd.png new file mode 100644 index 0000000..793fe7a Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/motd.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/options.png b/800OTClient/layouts/retro/images/topbuttons/options.png new file mode 100644 index 0000000..64a2186 Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/options.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/particles.png b/800OTClient/layouts/retro/images/topbuttons/particles.png new file mode 100644 index 0000000..2452eed Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/particles.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/prey.png b/800OTClient/layouts/retro/images/topbuttons/prey.png new file mode 100644 index 0000000..d5da451 Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/prey.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/prey_window.png b/800OTClient/layouts/retro/images/topbuttons/prey_window.png new file mode 100644 index 0000000..ddef6ee Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/prey_window.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/quest_tracker.png b/800OTClient/layouts/retro/images/topbuttons/quest_tracker.png new file mode 100644 index 0000000..0cacbc4 Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/quest_tracker.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/questlog.png b/800OTClient/layouts/retro/images/topbuttons/questlog.png new file mode 100644 index 0000000..105e529 Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/questlog.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/shop.png b/800OTClient/layouts/retro/images/topbuttons/shop.png new file mode 100644 index 0000000..21d5e95 Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/shop.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/skills.png b/800OTClient/layouts/retro/images/topbuttons/skills.png new file mode 100644 index 0000000..9424cea Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/skills.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/spelllist.png b/800OTClient/layouts/retro/images/topbuttons/spelllist.png new file mode 100644 index 0000000..b21d17a Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/spelllist.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/terminal.png b/800OTClient/layouts/retro/images/topbuttons/terminal.png new file mode 100644 index 0000000..3d113ea Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/terminal.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/unjustifiedpoints.png b/800OTClient/layouts/retro/images/topbuttons/unjustifiedpoints.png new file mode 100644 index 0000000..81de760 Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/unjustifiedpoints.png differ diff --git a/800OTClient/layouts/retro/images/topbuttons/viplist.png b/800OTClient/layouts/retro/images/topbuttons/viplist.png new file mode 100644 index 0000000..646091d Binary files /dev/null and b/800OTClient/layouts/retro/images/topbuttons/viplist.png differ diff --git a/800OTClient/layouts/retro/images/ui/arrow_horizontal.png b/800OTClient/layouts/retro/images/ui/arrow_horizontal.png new file mode 100644 index 0000000..a0ec72c Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/arrow_horizontal.png differ diff --git a/800OTClient/layouts/retro/images/ui/arrow_vertical.png b/800OTClient/layouts/retro/images/ui/arrow_vertical.png new file mode 100644 index 0000000..d48aba3 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/arrow_vertical.png differ diff --git a/800OTClient/layouts/retro/images/ui/broder_panel.png b/800OTClient/layouts/retro/images/ui/broder_panel.png new file mode 100644 index 0000000..da36bae Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/broder_panel.png differ diff --git a/800OTClient/layouts/retro/images/ui/button.png b/800OTClient/layouts/retro/images/ui/button.png new file mode 100644 index 0000000..3c8f17e Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/button.png differ diff --git a/800OTClient/layouts/retro/images/ui/checkbox.png b/800OTClient/layouts/retro/images/ui/checkbox.png new file mode 100644 index 0000000..829cc59 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/checkbox.png differ diff --git a/800OTClient/layouts/retro/images/ui/checkbox_round.png b/800OTClient/layouts/retro/images/ui/checkbox_round.png new file mode 100644 index 0000000..e45bae8 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/checkbox_round.png differ diff --git a/800OTClient/layouts/retro/images/ui/colorbox.png b/800OTClient/layouts/retro/images/ui/colorbox.png new file mode 100644 index 0000000..9d2ef7f Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/colorbox.png differ diff --git a/800OTClient/layouts/retro/images/ui/combobox.png b/800OTClient/layouts/retro/images/ui/combobox.png new file mode 100644 index 0000000..18dc0ac Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/combobox.png differ diff --git a/800OTClient/layouts/retro/images/ui/combobox_rounded.png b/800OTClient/layouts/retro/images/ui/combobox_rounded.png new file mode 100644 index 0000000..faf8607 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/combobox_rounded.png differ diff --git a/800OTClient/layouts/retro/images/ui/combobox_square.png b/800OTClient/layouts/retro/images/ui/combobox_square.png new file mode 100644 index 0000000..ec0ed70 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/combobox_square.png differ diff --git a/800OTClient/layouts/retro/images/ui/dark_background.png b/800OTClient/layouts/retro/images/ui/dark_background.png new file mode 100644 index 0000000..9b8e992 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/dark_background.png differ diff --git a/800OTClient/layouts/retro/images/ui/icon_add.png b/800OTClient/layouts/retro/images/ui/icon_add.png new file mode 100644 index 0000000..f820a0b Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/icon_add.png differ diff --git a/800OTClient/layouts/retro/images/ui/item.png b/800OTClient/layouts/retro/images/ui/item.png new file mode 100644 index 0000000..6b605a3 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/item.png differ diff --git a/800OTClient/layouts/retro/images/ui/menubarleft.png b/800OTClient/layouts/retro/images/ui/menubarleft.png new file mode 100644 index 0000000..4ca138b Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/menubarleft.png differ diff --git a/800OTClient/layouts/retro/images/ui/menubox.png b/800OTClient/layouts/retro/images/ui/menubox.png new file mode 100644 index 0000000..169e36d Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/menubox.png differ diff --git a/800OTClient/layouts/retro/images/ui/minibroder.png b/800OTClient/layouts/retro/images/ui/minibroder.png new file mode 100644 index 0000000..3dfe1b7 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/minibroder.png differ diff --git a/800OTClient/layouts/retro/images/ui/miniwindow.png b/800OTClient/layouts/retro/images/ui/miniwindow.png new file mode 100644 index 0000000..6886d44 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/miniwindow.png differ diff --git a/800OTClient/layouts/retro/images/ui/miniwindow_buttons.png b/800OTClient/layouts/retro/images/ui/miniwindow_buttons.png new file mode 100644 index 0000000..3fbb870 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/miniwindow_buttons.png differ diff --git a/800OTClient/layouts/retro/images/ui/noimage.png b/800OTClient/layouts/retro/images/ui/noimage.png new file mode 100644 index 0000000..efd98b7 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/noimage.png differ diff --git a/800OTClient/layouts/retro/images/ui/option_button.png b/800OTClient/layouts/retro/images/ui/option_button.png new file mode 100644 index 0000000..5e92446 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/option_button.png differ diff --git a/800OTClient/layouts/retro/images/ui/options_broder.png b/800OTClient/layouts/retro/images/ui/options_broder.png new file mode 100644 index 0000000..b7d7a36 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/options_broder.png differ diff --git a/800OTClient/layouts/retro/images/ui/panel_bottom.png b/800OTClient/layouts/retro/images/ui/panel_bottom.png new file mode 100644 index 0000000..c87e086 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/panel_bottom.png differ diff --git a/800OTClient/layouts/retro/images/ui/panel_bottom2.png b/800OTClient/layouts/retro/images/ui/panel_bottom2.png new file mode 100644 index 0000000..d69490c Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/panel_bottom2.png differ diff --git a/800OTClient/layouts/retro/images/ui/panel_flat.png b/800OTClient/layouts/retro/images/ui/panel_flat.png new file mode 100644 index 0000000..2da41dd Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/panel_flat.png differ diff --git a/800OTClient/layouts/retro/images/ui/panel_map.png b/800OTClient/layouts/retro/images/ui/panel_map.png new file mode 100644 index 0000000..ea2b90e Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/panel_map.png differ diff --git a/800OTClient/layouts/retro/images/ui/panel_side.png b/800OTClient/layouts/retro/images/ui/panel_side.png new file mode 100644 index 0000000..d7df3ba Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/panel_side.png differ diff --git a/800OTClient/layouts/retro/images/ui/panel_top.png b/800OTClient/layouts/retro/images/ui/panel_top.png new file mode 100644 index 0000000..e403577 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/panel_top.png differ diff --git a/800OTClient/layouts/retro/images/ui/progress_icons.png b/800OTClient/layouts/retro/images/ui/progress_icons.png new file mode 100644 index 0000000..4f34ef3 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/progress_icons.png differ diff --git a/800OTClient/layouts/retro/images/ui/progressbar.png b/800OTClient/layouts/retro/images/ui/progressbar.png new file mode 100644 index 0000000..503d340 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/progressbar.png differ diff --git a/800OTClient/layouts/retro/images/ui/progressbarhpmana.png b/800OTClient/layouts/retro/images/ui/progressbarhpmana.png new file mode 100644 index 0000000..3ce0657 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/progressbarhpmana.png differ diff --git a/800OTClient/layouts/retro/images/ui/progressbarskills.png b/800OTClient/layouts/retro/images/ui/progressbarskills.png new file mode 100644 index 0000000..d139932 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/progressbarskills.png differ diff --git a/800OTClient/layouts/retro/images/ui/rarity_blue.png b/800OTClient/layouts/retro/images/ui/rarity_blue.png new file mode 100644 index 0000000..b19e350 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/rarity_blue.png differ diff --git a/800OTClient/layouts/retro/images/ui/rarity_gold.png b/800OTClient/layouts/retro/images/ui/rarity_gold.png new file mode 100644 index 0000000..d7f8a07 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/rarity_gold.png differ diff --git a/800OTClient/layouts/retro/images/ui/rarity_green.png b/800OTClient/layouts/retro/images/ui/rarity_green.png new file mode 100644 index 0000000..d9cbbcc Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/rarity_green.png differ diff --git a/800OTClient/layouts/retro/images/ui/rarity_purple.png b/800OTClient/layouts/retro/images/ui/rarity_purple.png new file mode 100644 index 0000000..76fd299 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/rarity_purple.png differ diff --git a/800OTClient/layouts/retro/images/ui/rarity_white.png b/800OTClient/layouts/retro/images/ui/rarity_white.png new file mode 100644 index 0000000..1ee3eef Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/rarity_white.png differ diff --git a/800OTClient/layouts/retro/images/ui/scrollbar.png b/800OTClient/layouts/retro/images/ui/scrollbar.png new file mode 100644 index 0000000..4bbd1c0 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/scrollbar.png differ diff --git a/800OTClient/layouts/retro/images/ui/separator_horizontal.png b/800OTClient/layouts/retro/images/ui/separator_horizontal.png new file mode 100644 index 0000000..9eb47b3 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/separator_horizontal.png differ diff --git a/800OTClient/layouts/retro/images/ui/separator_vertical.png b/800OTClient/layouts/retro/images/ui/separator_vertical.png new file mode 100644 index 0000000..ca3b5d3 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/separator_vertical.png differ diff --git a/800OTClient/layouts/retro/images/ui/special_miniwindow.png b/800OTClient/layouts/retro/images/ui/special_miniwindow.png new file mode 100644 index 0000000..906d537 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/special_miniwindow.png differ diff --git a/800OTClient/layouts/retro/images/ui/spinbox.png b/800OTClient/layouts/retro/images/ui/spinbox.png new file mode 100644 index 0000000..cef075c Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/spinbox.png differ diff --git a/800OTClient/layouts/retro/images/ui/spinbox_down.png b/800OTClient/layouts/retro/images/ui/spinbox_down.png new file mode 100644 index 0000000..b124103 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/spinbox_down.png differ diff --git a/800OTClient/layouts/retro/images/ui/spinbox_up.png b/800OTClient/layouts/retro/images/ui/spinbox_up.png new file mode 100644 index 0000000..bf1b6d6 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/spinbox_up.png differ diff --git a/800OTClient/layouts/retro/images/ui/tabbutton_rounded.png b/800OTClient/layouts/retro/images/ui/tabbutton_rounded.png new file mode 100644 index 0000000..3c8f17e Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/tabbutton_rounded.png differ diff --git a/800OTClient/layouts/retro/images/ui/tabbutton_square.png b/800OTClient/layouts/retro/images/ui/tabbutton_square.png new file mode 100644 index 0000000..f669be6 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/tabbutton_square.png differ diff --git a/800OTClient/layouts/retro/images/ui/textedit.png b/800OTClient/layouts/retro/images/ui/textedit.png new file mode 100644 index 0000000..9b35c60 Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/textedit.png differ diff --git a/800OTClient/layouts/retro/images/ui/window.png b/800OTClient/layouts/retro/images/ui/window.png new file mode 100644 index 0000000..167690e Binary files /dev/null and b/800OTClient/layouts/retro/images/ui/window.png differ diff --git a/800OTClient/layouts/retro/styles/10-checkboxes.otui b/800OTClient/layouts/retro/styles/10-checkboxes.otui new file mode 100644 index 0000000..1998acb --- /dev/null +++ b/800OTClient/layouts/retro/styles/10-checkboxes.otui @@ -0,0 +1,65 @@ +CheckBox < UICheckBox + size: 12 12 + text-align: left + text-offset: 18 -1 + color: #dfdfdf + image-color: #dfdfdfff + image-rect: 0 0 12 12 + image-source: /images/ui/checkbox + change-cursor-image: true + cursor: pointer + + $hover !disabled: + color: #ffffff + + $!checked: + image-clip: 0 0 12 12 + + $checked: + image-clip: 0 12 12 12 + + $disabled: + image-color: #dfdfdf88 + color: #dfdfdf88 + opacity: 0.8 + change-cursor-image: false + +ColorBox < UICheckBox + size: 16 16 + image-color: #dfdfdfff + image-source: /images/ui/colorbox + + $checked: + image-clip: 16 0 16 16 + + $!checked: + image-clip: 0 0 16 16 + +ButtonBox < UICheckBox + font: verdana-11px-antialised + color: white + size: 106 23 + text-offset: 0 0 + text-align: center + image-source: /images/ui/button + image-color: #dfdfdf + image-clip: 0 0 22 23 + image-border: 3 + change-cursor-image: true + cursor: pointer + + $hover !disabled: + image-clip: 0 23 22 23 + + $checked: + image-clip: 0 46 22 23 + color: white + text-offset: 2 2 + + $disabled: + color: #dfdfdf88 + image-color: #dfdfdf88 + change-cursor-image: false + +ButtonBoxRounded < ButtonBox + image-source: /images/ui/button_rounded \ No newline at end of file diff --git a/800OTClient/layouts/retro/styles/10-comboboxes.otui b/800OTClient/layouts/retro/styles/10-comboboxes.otui new file mode 100644 index 0000000..a94cbce --- /dev/null +++ b/800OTClient/layouts/retro/styles/10-comboboxes.otui @@ -0,0 +1,106 @@ +ComboBoxPopupScrollMenuButton < UIButton + height: 23 + font: verdana-11px-antialised + text-align: left + text-offset: 4 0 + color: #BFBFBF + background-color: alpha + margin: 1 + + $hover !disabled: + color: #dfdfdf + background-color: #595959 + + $disabled: + color: #dfdfdf88 + +ComboBoxPopupScrollMenu < UIPopupScrollMenu + image-source: /images/ui/combobox_square + image-clip: 0 69 91 23 + image-border: 1 + +ComboBoxPopupMenuButton < UIButton + height: 23 + font: verdana-11px-antialised + text-align: left + text-offset: 5 2 + color: #dfdfdf + background-color: alpha + margin: 1 + + $hover !disabled: + color: #dfdfdf + background-color: #355d89 + + $disabled: + color: #dfdfdf88 + +ComboBoxPopupMenu < UIPopupMenu + image-source: /images/ui/combobox_square + image-clip: 0 69 91 23 + image-border: 1 + +ComboBox < UIComboBox + font: verdana-11px-antialised + color: #dfdfdf + size: 91 23 + text-offset: 5 2 + text-align: left + image-source: /images/ui/combobox_square + image-border: 3 + image-border-right: 19 + image-clip: 0 0 91 23 + + $hover !disabled: + image-clip: 0 23 91 23 + + $on: + image-clip: 0 46 91 23 + + $disabled: + color: #dfdfdf88 + opacity: 0.8 + +ComboBoxRoundedPopupScrollMenuButton < UIButton + height: 23 + font: verdana-11px-antialised + text-align: left + text-offset: 5 2 + color: #dfdfdf + background-color: alpha + + $hover !disabled: + color: #ffffff + background-color: #355d89 + + $disabled: + color: #dfdfdf88 + +ComboBoxRoundedPopupScrollMenu < UIPopupScrollMenu + image-source: /images/ui/combobox_rounded + image-clip: 0 69 91 23 + image-border: 3 + +ComboBoxRoundedPopupMenuButton < UIButton + height: 23 + font: verdana-11px-antialised + text-align: left + text-offset: 5 2 + color: #dfdfdf + background-color: alpha + + $hover !disabled: + color: #ffffff + background-color: #355d89 + + $disabled: + color: #dfdfdf88 + +ComboBoxRoundedPopupMenu < UIPopupMenu + image-source: /images/ui/combobox_rounded + image-clip: 0 69 91 23 + image-border: 3 + +ComboBoxRounded < ComboBox + image-source: /images/ui/combobox_rounded + image-border: 3 diff --git a/800OTClient/layouts/retro/styles/10-panels.otui b/800OTClient/layouts/retro/styles/10-panels.otui new file mode 100644 index 0000000..15fbbc3 --- /dev/null +++ b/800OTClient/layouts/retro/styles/10-panels.otui @@ -0,0 +1,19 @@ +Panel < UIWidget + phantom: true + auto-focus: first + +ScrollablePanel < UIScrollArea + phantom: true + auto-focus: first + +FlatPanel < Panel + image-source: /images/ui/panel_flat + image-border: 1 + +ScrollableFlatPanel < ScrollablePanel + image-source: /images/ui/panel_flat + image-border: 1 + +LightFlatPanel < Panel + image-source: /images/ui/panel_lightflat + image-border: 1 diff --git a/800OTClient/layouts/retro/styles/10-progressbars.otui b/800OTClient/layouts/retro/styles/10-progressbars.otui new file mode 100644 index 0000000..4a8af2f --- /dev/null +++ b/800OTClient/layouts/retro/styles/10-progressbars.otui @@ -0,0 +1,38 @@ +ProgressBar < UIProgressBar + height: 16 + background-color: red + image-source: /images/ui/progressbar + image-border: 2 + font: verdana-11px-rounded + text-offset: 0 2 + + $!on: + visible: false + margin-top: 0 + margin-bottom: 0 + height: 0 + +LifeProgressBar < UIProgressBar + height: 16 + background-color: green + border: 1 black + font: verdana-11px-rounded + text-offset: 0 2 + margin: 2 + +ProgressRect < UIProgressRect + anchors.fill: parent + phantom: true + color: white + background-color: #00000088 + font: verdana-11px-rounded + +HealthBar < ProgressBar + image-source: /images/ui/progressbarhpmana + image-border: 3 + background-color: #ff4444 + +ManaBar < ProgressBar + image-source: /images/ui/progressbarhpmana + image-border: 4 + background-color: #4444ff diff --git a/800OTClient/layouts/retro/styles/10-textedits.otui b/800OTClient/layouts/retro/styles/10-textedits.otui new file mode 100644 index 0000000..c0071db --- /dev/null +++ b/800OTClient/layouts/retro/styles/10-textedits.otui @@ -0,0 +1,22 @@ +TextEdit < UITextEdit + font: verdana-11px-antialised + color: white + size: 86 22 + text-offset: 0 4 + opacity: 1 + padding: 4 + image-source: /images/ui/textedit + image-border: 1 + selection-color: #272727 + selection-background-color: #cccccc + change-cursor-image: true + $disabled: + color: #27272788 + opacity: 0.5 + change-cursor-image: false + +PasswordTextEdit < TextEdit + text-hidden: true + +MultilineTextEdit < TextEdit + multiline: true diff --git a/800OTClient/layouts/retro/styles/10-windows.otui b/800OTClient/layouts/retro/styles/10-windows.otui new file mode 100644 index 0000000..9d05389 --- /dev/null +++ b/800OTClient/layouts/retro/styles/10-windows.otui @@ -0,0 +1,32 @@ +Window < UIWindow + font: verdana-11px-antialised + size: 236 207 + opacity: 1 + color: #AFAFAF + text-offset: 0 2 + text-align: top + image-source: /images/ui/window + image-border: 4 + image-border-top: 17 + padding-top: 25 + padding-left: 16 + padding-right: 16 + padding-bottom: 16 + + $disabled: + color: #dfdfdf + +HeadlessWindow < UIWindow + image-source: /images/ui/window_headless + image-border: 5 + padding: 5 + +MainWindow < Window + anchors.centerIn: parent + +StaticWindow < Window + &static: true + +StaticMainWindow < StaticWindow + anchors.centerIn: parent + \ No newline at end of file diff --git a/800OTClient/layouts/retro/styles/20-popupmenus.otui b/800OTClient/layouts/retro/styles/20-popupmenus.otui new file mode 100644 index 0000000..ceeb532 --- /dev/null +++ b/800OTClient/layouts/retro/styles/20-popupmenus.otui @@ -0,0 +1,83 @@ +PopupMenuButton < UIButton + height: 18 + size: 0 21 + text-offset: 4 2 + text-align: left + font: verdana-11px-antialised + + color: white + background-color: alpha + + $hover !disabled: + color: #ffffff + background-color: #ffffff44 + image-clip: 0 40 20 20 + + $disabled: + color: #555555 + +PopupMenuShortcutLabel < Label + font: verdana-11px-antialised + text-align: right + anchors.fill: parent + margin-right: 2 + margin-left: 5 + +PopupMenuSeparator < UIWidget + margin-left: 2 + margin-right: 2 + margin-bottom: 1 + image-source: /images/ui/separator_horizontal + image-border-left: 1 + image-border-right: 1 + image-clip: 0 0 32 2 + height: 2 + phantom: true + +PopupMenu < UIPopupMenu + width: 120 + image-source: /images/ui/menubox + image-border: 3 + padding: 5 + +PopupScrollMenuButton < UIButton + height: 18 + size: 0 21 + text-offset: 4 0 + text-align: left + font: verdana-11px-antialised + + color: #aaaaaa + background-color: alpha + + $hover !disabled: + color: #ffffff + background-color: #ffffff44 + image-clip: 0 40 20 20 + + $disabled: + color: #555555 + +PopupScrollMenuShortcutLabel < Label + font: verdana-11px-antialised + text-align: right + anchors.fill: parent + margin-right: 2 + margin-left: 5 + +PopupScrollMenuSeparator < UIWidget + margin-left: 2 + margin-right: 2 + margin-bottom: 1 + image-source: /images/ui/menubox + image-border-left: 1 + image-border-right: 1 + image-clip: 0 0 32 2 + height: 2 + phantom: true + +PopupScrollMenu < UIPopupScrollMenu + width: 50 + image-source: /images/ui/menubox + image-border: 3 + padding: 3 diff --git a/800OTClient/layouts/retro/styles/20-tabbars.otui b/800OTClient/layouts/retro/styles/20-tabbars.otui new file mode 100644 index 0000000..a819635 --- /dev/null +++ b/800OTClient/layouts/retro/styles/20-tabbars.otui @@ -0,0 +1,113 @@ +MoveableTabBar < UIMoveableTabBar + size: 80 21 +MoveableTabBarPanel < Panel +MoveableTabBarButton < UIButton + size: 96 18 + image-source: /images/ui/tabbutton_square + image-clip: 0 0 96 22 + image-border: 3 + image-border-bottom: 0 + color: #7F7F7F + anchors.top: parent.top + anchors.left: parent.left + margin-top: 2 + padding: 3 + + $checked: + image-clip: 0 36 96 22 + color: #dfdfdf + + $on !checked: + color: #F75F5F + +TabBar < UITabBar + size: 80 21 + Panel + id: buttonsPanel + anchors.fill: parent +TabBarPanel < Panel +TabBarButton < UIButton + size: 17 18 + image-source: /images/ui/tabbutton_square + image-color: #dfdfdf + image-clip: 0 0 98 18 + image-border: 3 + icon-color: #dfdfdf + color: #dfdfdf + anchors.top: parent.top + padding: 5 + + $first: + anchors.left: parent.left + + $!first: + anchors.left: prev.right + margin-left: 5 + + $hover !checked: + image-clip: 0 18 98 18 + color: #dfdfdf + + $disabled: + image-color: #dfdfdf88 + icon-color: #dfdfdf + + $checked: + image-clip: 0 36 98 18 + color: #dfdfdf + + $on !checked: + color: #de6f6f + +TabBarRounded < TabBar +TabBarRoundedPanel < TabBarPanel +TabBarRoundedButton < TabBarButton + image-source: /images/ui/tabbutton_rounded + +TabBarVertical < UITabBar + width: 96 + ScrollableFlatPanel + id: buttonsPanel + anchors.top: parent.top + anchors.left: parent.left + anchors.right: scrollBar.left + anchors.bottom: parent.bottom + vertical-scrollbar: scrollBar + margin-right: 1 + padding-top: 10 + + VerticalScrollBar + id: scrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 16 + pixels-scroll: true + $!on: + width: 0 + +TabBarVerticalPanel < Panel +TabBarVerticalButton < UIButton + size: 48 48 + color: #aaaaaa + anchors.left: parent.left + anchors.right: parent.right + text-align: bottom + icon-align: top + icon-offset-y: 2 + icon-color: #888888 + $first: + anchors.top: parent.top + $!first: + anchors.top: prev.bottom + margin-top: 10 + $hover !checked: + color: white + icon-color: #dfdfdf + $disabled: + icon-color: #333333 + $checked: + icon-color: #ffffff + color: #80c7f8 + $on !checked: + color: #F55E5E \ No newline at end of file diff --git a/800OTClient/layouts/retro/styles/20-topmenu.otui b/800OTClient/layouts/retro/styles/20-topmenu.otui new file mode 100644 index 0000000..0e6657a --- /dev/null +++ b/800OTClient/layouts/retro/styles/20-topmenu.otui @@ -0,0 +1,131 @@ +TopButton < UIButton + size: 26 26 + image-source: /images/ui/button_top + image-clip: 0 0 26 26 + image-border: 3 + image-color: #ffffffff + icon-color: #ffffffff + icon-clip: 0 0 20 20 + + $on: + image-source: /images/ui/button_top_blink + icon-clip: 0 20 20 20 + + $hover !disabled: + image-color: #ffffff99 + image-clip: 26 0 26 26 + + $pressed: + image-clip: 52 0 26 26 + icon-clip: 0 20 20 20 + + $disabled: + image-color: #ffffff44 + icon-color: #ffffff44 + +TopToggleButton < UIButton + size: 20 20 + image-source: /images/ui/button_top + image-clip: 0 0 26 26 + image-color: #ffffffff + image-border: 3 + icon-clip: 0 0 20 20 + icon-color: #ffffffff + + $on: + icon-clip: 0 20 20 20 + + $hover !disabled: + image-color: #ffffff99 + image-clip: 26 0 26 26 + + $pressed: + image-clip: 52 0 26 26 + icon-clip: 0 20 20 20 + + $disabled: + image-color: #ffffff44 + icon-color: #ffffff44 + +TopMenuButtonsPanel < Panel + layout: + type: horizontalBox + spacing: 4 + fit-children: true + padding: 6 4 + +TopMenuPanel < Panel + height: 36 + image-source: /images/ui/panel_top + image-repeated: true + image-border: 3 + image-border-top: 0 + focusable: false + +TopMenuFrameCounterLabel < Label + font: verdana-11px-rounded + color: white + margin-top: 4 + margin-left: 5 + +TopMenuPingLabel < Label + font: verdana-11px-rounded + +TopMenu < TopMenuPanel + id: topMenu + width: 800 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + &hideIngame: true + &reverseButtons: true + + UIWidget + id: discord + anchors.top: parent.top + anchors.left: parent.left + margin-top: 3 + margin-left: 5 + image-source: /images/ui/discord + + Label + id: discordLabel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: prev.right + text-align: center + margin-left: 2 + text-auto-resize: true + + TopMenuButtonsPanel + id: rightButtonsPanel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: prev.right + + TopMenuButtonsPanel + id: rightGameButtonsPanel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: prev.right + visible: false + + Label + id: onlineLabel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + text-align: center + text-auto-resize: true + + TopMenuButtonsPanel + id: leftButtonsPanel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + + TopMenuButtonsPanel + id: leftGameButtonsPanel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: prev.left + visible: false diff --git a/800OTClient/layouts/retro/styles/30-miniwindow.otui b/800OTClient/layouts/retro/styles/30-miniwindow.otui new file mode 100644 index 0000000..3742701 --- /dev/null +++ b/800OTClient/layouts/retro/styles/30-miniwindow.otui @@ -0,0 +1,215 @@ +MiniWindow < UIMiniWindow + font: verdana-11px-antialised + icon-rect: 4 2 13 13 + icon-clip: 0 0 20 20 + color: #9F9F9F + width: 190 + height: 200 + text-offset: 24 2 + text-align: topLeft + image-source: /images/ui/miniwindow + image-border: 4 + image-border-top: 20 + image-border-bottom: 4 + focusable: false + &minimizedHeight: 20 + + $on: + image-border-bottom: 2 + + UIWidget + id: miniwindowTopBar + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + margin-right: 3 + margin-left: 3 + margin-top: 3 + size: 258 14 + phantom: true + + UIButton + id: closeButton + anchors.top: parent.top + anchors.right: parent.right + margin-top: 2 + margin-right: 4 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 28 0 14 14 + + $hover: + image-clip: 28 14 14 14 + + $pressed: + image-clip: 28 28 14 14 + + UIButton + id: minimizeButton + anchors.top: closeButton.top + anchors.right: closeButton.left + margin-right: 3 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 0 0 14 14 + + $hover: + image-clip: 0 14 14 14 + + $pressed: + image-clip: 0 28 14 14 + + $on: + image-clip: 14 0 14 14 + + $on hover: + image-clip: 14 14 14 14 + + $on pressed: + image-clip: 14 28 14 14 + + UIButton + id: lockButton + anchors.top: minimizeButton.top + anchors.right: minimizeButton.left + margin-right: 2 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 98 0 14 14 + + $hover: + image-clip: 98 14 14 14 + + $pressed: + image-clip: 98 28 14 14 + + $on: + image-clip: 84 0 14 14 + + $on hover: + image-clip: 84 14 14 14 + + $on pressed: + image-clip: 84 28 14 14 + + VerticalScrollBar + id: miniwindowScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 14 + margin-top: 18 + margin-right: 4 + margin-bottom: 3 + pixels-scroll: true + + $!on: + width: 0 + + ResizeBorder + id: bottomResizeBorder + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 3 + minimum: 48 + margin-left: 3 + margin-right: 3 + background: #ffffff88 + +MiniWindowContents < ScrollablePanel + id: contentsPanel + anchors.fill: parent + anchors.right: miniwindowScrollBar.left + margin-left: 3 + margin-bottom: 3 + margin-top: 18 + margin-right: 1 + vertical-scrollbar: miniwindowScrollBar + +HeadlessMiniWindow < UIMiniWindow + font: verdana-11px-antialised + icon-rect: 4 2 13 12 + icon-clip: 0 0 20 20 + color: #8F8F8F + width: 190 + height: 200 + focusable: false + &minimizedHeight: 20 + + $on: + image-border-bottom: 2 + + $!on: + text: + icon: + + UIButton + id: minimizeButton + anchors.top: parent.top + anchors.right: parent.right + margin-right: 10 + margin-top: 1 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 0 0 14 14 + + $hover: + image-clip: 0 14 14 14 + + $pressed: + image-clip: 0 28 14 14 + + $on: + image-clip: 14 0 14 14 + + $on hover: + image-clip: 14 14 14 14 + + $on pressed: + image-clip: 14 28 14 14 + + $!on: + size: 0 0 + + UIWidget + id: miniwindowTopBar + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + margin-right: 3 + margin-left: 3 + margin-top: 3 + size: 258 14 + phantom: true + + UIButton + id: closeButton + anchors.top: parent.top + anchors.right: parent.right + hidden: true + + VerticalScrollBar + id: miniwindowScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 14 + margin-top: 16 + margin-right: 4 + margin-bottom: 3 + pixels-scroll: true + + $!on: + width: 0 + + ResizeBorder + id: bottomResizeBorder + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 3 + minimum: 48 + margin-left: 3 + margin-right: 3 + background: #ffffff88 diff --git a/800OTClient/layouts/retro/styles/40-console.otui b/800OTClient/layouts/retro/styles/40-console.otui new file mode 100644 index 0000000..7943fe1 --- /dev/null +++ b/800OTClient/layouts/retro/styles/40-console.otui @@ -0,0 +1,219 @@ +ConsoleLabel < UITextEdit + font: verdana-11px-antialised + height: 14 + color: yellow + margin-left: 1 + text-wrap: true + text-auto-resize: true + selection-color: #111416 + selection-background-color: #808080 + change-cursor-image: false + cursor-visible: false + editable: false + draggable: true + selectable: false + focusable: false + +ConsolePhantomLabel < UILabel + font: verdana-11px-antialised + height: 14 + color: yellow + text-wrap: true + text-auto-resize: true + selection-color: #111416 + selection-background-color: #999999 + +ConsoleTabBar < MoveableTabBar + height: 16 + +ConsoleTabBarPanel < MoveableTabBarPanel + id: consoleTab + + ScrollablePanel + id: consoleBuffer + anchors.fill: parent + margin-right: 12 + vertical-scrollbar: consoleScrollBar + layout: + type: verticalBox + align-bottom: true + border-width: 1 + border-color: #202327 + background: #00000066 + inverted-scroll: true + padding: 1 + + VerticalScrollBar + id: consoleScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 14 + pixels-scroll: true + +ConsoleTabBarButton < MoveableTabBarButton + height: 16 + padding: 15 + +ConsolePanel < Panel + image-source: /images/ui/panel_bottom + image-border: 7 + image-border-top: 29 + + $first: + anchors.fill: parent + + $!first: + anchors.top: prev.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + + CheckBox + id: toggleChat + !tooltip: tr('Disable chat mode, allow to walk using ASDW') + anchors.left: parent.left + anchors.top: parent.top + margin-left: 6 + margin-top: 3 + @onCheckChange: toggleChat() + + TabButton + id: prevChannelButton + icon: /images/game/console/leftarrow + anchors.left: toggleChat.right + anchors.top: parent.top + margin-top: 1 + size: 16 16 + + ConsoleTabBar + id: consoleTabBar + anchors.left: prev.right + anchors.top: parent.top + anchors.right: next.left + margin-top: 0 + tab-spacing: 2 + movable: true + + TabButton + id: nextChannelButton + icon: /images/game/console/rightarrow + anchors.right: next.left + anchors.top: parent.top + margin-top: 1 + size: 16 16 + margin-right: 5 + + TabButton + id: closeChannelButton + !tooltip: tr('Close this channel') .. ' (Ctrl+E)' + icon: /images/game/console/closechannel + anchors.right: next.left + anchors.top: parent.top + enabled: false + margin-right: 5 + margin-top: 1 + size: 16 16 + icon-clip: 0 0 16 16 + + $pressed: + icon-clip: 0 16 16 16 + + @onClick: removeCurrentTab() + + TabButton + id: clearChannelButton + !tooltip: tr('Clear current message window') + icon: /images/game/console/clearchannel + anchors.right: next.left + anchors.top: parent.top + margin-right: 5 + margin-top: 1 + size: 16 16 + icon-clip: 0 0 16 16 + + $pressed: + icon-clip: 0 16 16 16 + + @onClick: | + local consoleTabBar = self:getParent():getChildById('consoleTabBar') + clearChannel(consoleTabBar) + + TabButton + id: channelsButton + !tooltip: tr('Open new channel') .. ' (Ctrl+O)' + icon: /images/game/console/channels + anchors.right: next.left + anchors.top: parent.top + margin-right: 5 + margin-top: 1 + size: 16 16 + icon-clip: 0 0 16 16 + + $pressed: + icon-clip: 0 16 16 16 + + @onClick: g_game.requestChannels() + + TabButton + id: ignoreButton + !tooltip: tr('Ignore players') + icon: /images/game/console/ignore + anchors.right: parent.right + anchors.top: parent.top + margin-right: 5 + margin-top: 1 + size: 16 16 + icon-clip: 0 0 16 16 + + $pressed: + icon-clip: 0 16 16 16 + + @onClick: onClickIgnoreButton() + + Panel + id: consoleContentPanel + anchors.top: consoleTabBar.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: consoleTextEdit.top + margin-left: 6 + margin-right: 6 + margin-bottom: 2 + margin-top: 6 + padding: 1 + focusable: false + phantom: true + + TabButton + id: sayModeButton + icon: /images/game/console/say + !tooltip: tr('Adjust volume') + &sayMode: 2 + size: 18 18 + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-left: 8 + margin-bottom: 4 + @onClick: sayModeChange() + + HorizontalSeparator + id: separator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: prev.top + margin-bottom: 3 + margin-left: 7 + margin-right: 7 + + TextEdit + id: consoleTextEdit + anchors.left: sayModeButton.right + anchors.right: parent.right + anchors.bottom: parent.bottom + margin-right: 7 + margin-left: 2 + margin-bottom: 2 + shift-navigation: true + max-length: 255 + text-auto-submit: true diff --git a/800OTClient/layouts/retro/styles/40-entergame.otui b/800OTClient/layouts/retro/styles/40-entergame.otui new file mode 100644 index 0000000..9f82297 --- /dev/null +++ b/800OTClient/layouts/retro/styles/40-entergame.otui @@ -0,0 +1,3 @@ +EnterGameWindow < StaticMainWindow + !text: tr('Enter Game') + size: 260 340 \ No newline at end of file diff --git a/800OTClient/layouts/retro/styles/40-gamebuttons.otui b/800OTClient/layouts/retro/styles/40-gamebuttons.otui new file mode 100644 index 0000000..9a0a06a --- /dev/null +++ b/800OTClient/layouts/retro/styles/40-gamebuttons.otui @@ -0,0 +1,16 @@ +GameButtonsWindow < HeadlessMiniWindow + height: 26 + &forceOpen: true + &autoOpen: 4 + + MiniWindowContents + margin-top: 2 + + Panel + id: buttons + anchors.fill: parent + layout: + type: grid + cell-spacing: 3 + cell-size: 20 20 + flow: true \ No newline at end of file diff --git a/800OTClient/layouts/retro/styles/40-healthinfo.otui b/800OTClient/layouts/retro/styles/40-healthinfo.otui new file mode 100644 index 0000000..b3b4c5f --- /dev/null +++ b/800OTClient/layouts/retro/styles/40-healthinfo.otui @@ -0,0 +1,154 @@ +ExperienceBar < ProgressBar + id: experienceBar + background-color: #B6E866 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin: 1 + margin-top: 3 + +SoulLabel < GameLabel + id: soulLabel + text-align: right + color: white + font: verdana-11px-rounded + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.horizontalCenter + margin-top: 5 + margin-right: 3 + on: true + + $!on: + visible: false + margin-top: 0 + height: 0 + +CapLabel < GameLabel + id: capLabel + color: white + font: verdana-11px-rounded + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.horizontalCenter + margin-top: 5 + margin-left: 3 + on: true + + $!on: + visible: false + margin-top: 0 + height: 0 + +ConditionWidget < UIWidget + size: 18 18 + + $!first: + margin-left: 2 + +HealthOverlay < UIWidget + id: healthOverlay + anchors.fill: parent + phantom: true + + HealthBar + id: topHealthBar + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.horizontalCenter + phantom: true + + ManaBar + id: topManaBar + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.horizontalCenter + phantom: true + + UIProgressBar + id: healthCircle + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/left_empty + margin-right: 169 + margin-bottom: 16 + opacity: 0.5 + phantom: true + + UIProgressBar + id: healthCircleFront + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/left_full + margin-right: 169 + margin-bottom: 16 + opacity: 0.5 + phantom: true + + UIProgressBar + id: manaCircle + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/right_empty + margin-left: 130 + margin-bottom: 16 + opacity: 0.5 + phantom: true + + UIProgressBar + id: manaCircleFront + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/right_full + margin-left: 130 + margin-bottom: 16 + opacity: 0.4 + image-color: #0000FFFF + phantom: true + +HealthInfoWindow < HeadlessMiniWindow + icon: + text: + height: 100 + &forceOpen: true + icon: /images/topbuttons/healthinfo + !text: tr('Health Info') + + MiniWindowContents + margin-top: 2 + + HealthBar + id: healthBar + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + margin: 2 + margin-top: 0 + phantom: true + + ManaBar + id: manaBar + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin: 2 + margin-bottom: 0 + phantom: true + + ExperienceBar + Panel + id: conditionPanel + layout: + type: horizontalBox + height: 22 + margin-top: 4 + padding: 2 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + border-width: 1 + border-color: #00000077 + background-color: #ffffff11 + SoulLabel + CapLabel + diff --git a/800OTClient/layouts/retro/styles/40-inventory.otui b/800OTClient/layouts/retro/styles/40-inventory.otui new file mode 100644 index 0000000..4d81dcd --- /dev/null +++ b/800OTClient/layouts/retro/styles/40-inventory.otui @@ -0,0 +1,333 @@ +InventoryItem < Item + $on: + image-source: /images/ui/item-blessed + +HeadSlot < InventoryItem + id: slot1 + image-source: /images/game/slots/head + &position: {x=65535, y=1, z=0} + $on: + image-source: /images/game/slots/head-blessed + +BodySlot < InventoryItem + id: slot4 + image-source: /images/game/slots/body + &position: {x=65535, y=4, z=0} + $on: + image-source: /images/game/slots/body-blessed + +LegSlot < InventoryItem + id: slot7 + image-source: /images/game/slots/legs + &position: {x=65535, y=7, z=0} + $on: + image-source: /images/game/slots/legs-blessed + +FeetSlot < InventoryItem + id: slot8 + image-source: /images/game/slots/feet + &position: {x=65535, y=8, z=0} + $on: + image-source: /images/game/slots/feet-blessed + +NeckSlot < InventoryItem + id: slot2 + image-source: /images/game/slots/neck + &position: {x=65535, y=2, z=0} + $on: + image-source: /images/game/slots/neck-blessed + +LeftSlot < InventoryItem + id: slot6 + image-source: /images/game/slots/left-hand + &position: {x=65535, y=6, z=0} + $on: + image-source: /images/game/slots/left-hand-blessed + +FingerSlot < InventoryItem + id: slot9 + image-source: /images/game/slots/finger + &position: {x=65535, y=9, z=0} + $on: + image-source: /images/game/slots/finger-blessed + +BackSlot < InventoryItem + id: slot3 + image-source: /images/game/slots/back + &position: {x=65535, y=3, z=0} + $on: + image-source: /images/game/slots/back-blessed + +RightSlot < InventoryItem + id: slot5 + image-source: /images/game/slots/right-hand + &position: {x=65535, y=5, z=0} + $on: + image-source: /images/game/slots/right-hand-blessed + +AmmoSlot < InventoryItem + id: slot10 + image-source: /images/game/slots/ammo + &position: {x=65535, y=10, z=0} + $on: + image-source: /images/game/slots/ammo-blessed + +PurseButton < UIButton + id: purseButton + size: 34 12 + !tooltip: tr('Open purse') + icon-source: /images/game/slots/purse + icon-clip: 0 0 34 12 + + $on: + icon-clip: 0 12 34 12 + + $pressed: + icon-clip: 0 12 34 12 + +CombatBox < UICheckBox + size: 20 20 + image-clip: 0 0 20 20 + margin-left: 4 + + $checked: + image-clip: 0 20 20 20 + + +InventoryButton < Button + height: 18 + margin-top: 2 + text-align: center + font: cipsoftFont + color: white + size: 45 18 + text-offset: 2 2 + +SoulCapLabel < GameLabel + text-align: center + color: #FFFFFF + font: cipsoftFont + margin-top: 4 + text-offset: 0 3 + width: 36 + height: 20 + icon-source: /images/game/slots/soulcap + +FightOffensiveBox < CombatBox + image-source: /images/game/combatmodes/fightoffensive +FightBalancedBox < CombatBox + image-source: /images/game/combatmodes/fightbalanced +FightDefensiveBox < CombatBox + image-source: /images/game/combatmodes/fightdefensive +ChaseModeBox < CombatBox + image-source: /images/game/combatmodes/chasemode +SafeFightBox < CombatBox + image-source: /images/game/combatmodes/safefight + +MountButton < CombatBox + image-source: /images/game/combatmodes/mount + +InventoryWindow < HeadlessMiniWindow + icon: /images/topbuttons/inventory + height: 178 + id: inventoryWindow + @onClose: modules.game_inventory.onMiniWindowClose() + &save: true + &autoOpen: 3 + &forceOpen: true + + MiniWindowContents + anchors.left: parent.left + margin-top: 0 + + UIButton + id: minimizeButton + anchors.top: parent.top + anchors.left: parent.left + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 0 0 14 14 + margin-top: 3 + margin-left: 4 + + $hover: + image-clip: 0 14 14 14 + + $pressed: + image-clip: 0 28 14 14 + + $on: + image-clip: 14 0 14 14 + + $on hover: + image-clip: 14 14 14 14 + + $on pressed: + image-clip: 14 28 14 14 + + @onClick: | + self:getParent():getParent().minimizeButton:onClick() + + Panel + id: inventoryPanel + margin-right: 63 + margin-top: 2 + anchors.fill: parent + + HeadSlot + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 3 + + BodySlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + LegSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + FeetSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + NeckSlot + anchors.top: slot1.top + anchors.right: slot1.left + margin-top: 13 + margin-right: 5 + + LeftSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + FingerSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + BackSlot + anchors.top: slot1.top + anchors.left: slot1.right + margin-top: 13 + margin-left: 5 + + RightSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + AmmoSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + SoulCapLabel + id: soulLabel + anchors.top: slot10.bottom + anchors.horizontalCenter: slot10.horizontalCenter + + SoulCapLabel + id: capLabel + anchors.top: slot9.bottom + anchors.horizontalCenter: slot9.horizontalCenter + + PurseButton + anchors.left: slot3.left + anchors.bottom: slot3.top + margin-bottom: 3 + + Panel + id: conditionPanel + layout: + type: horizontalBox + height: 22 + padding: 2 + anchors.top: slot8.bottom + anchors.left: slot6.left + anchors.right: slot5.right + margin-top: 4 + border-width: 1 + border-color: #00000077 + background-color: #ffffff22 + + Panel + margin-top: 5 + anchors.fill: parent + anchors.left: prev.right + + FightOffensiveBox + id: fightOffensiveBox + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + ChaseModeBox + id: chaseModeBox + anchors.left: prev.right + anchors.top: parent.top + + FightBalancedBox + id: fightBalancedBox + margin-top: 22 + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + SafeFightBox + id: safeFightBox + margin-top: 22 + anchors.left: prev.right + anchors.top: parent.top + + FightDefensiveBox + id: fightDefensiveBox + margin-top: 44 + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + MountButton + id: mountButton + margin-top: 44 + anchors.left: prev.right + anchors.top: parent.top + + Panel + id: buttonsPanel + margin-top: 4 + margin-right: 5 + anchors.fill: parent + anchors.top: prev.bottom + layout: + type: verticalBox + + UIButton + id: buttonPvp + height: 20 + icon: /images/game/combatmodes/pvp + icon-clip: 0 0 42 20 + + $on: + icon-clip: 0 20 42 20 + + InventoryButton + !text: tr('Stop') + @onClick: g_game.stop(); g_game.cancelAttackAndFollow() + + InventoryButton + !text: tr('Options') + @onClick: modules.client_options.toggle() + + InventoryButton + !text: tr('Hotkeys') + @onClick: modules.game_hotkeys.toggle() + + InventoryButton + !text: tr('Logout') + @onClick: modules.game_interface.tryLogout() + diff --git a/800OTClient/layouts/retro/styles/40-minimap.otui b/800OTClient/layouts/retro/styles/40-minimap.otui new file mode 100644 index 0000000..cc10a8d --- /dev/null +++ b/800OTClient/layouts/retro/styles/40-minimap.otui @@ -0,0 +1,261 @@ +MinimapFlag < UIWidget + size: 11 11 + focusable: false + +MinimapCross < UIWidget + focusable: false + phantom: true + image: /images/game/minimap/cross + size: 16 16 + +MinimapFloorUpButton < Button + size: 20 20 + margin-right: 28 + margin-bottom: 28 + anchors.right: parent.right + anchors.bottom: parent.bottom + icon-source: /images/game/minimap/floor_up + icon-clip: 0 32 16 16 + $pressed: + icon-clip: 0 0 16 16 + $hover !pressed: + icon-clip: 0 16 16 16 + +MinimapFloorDownButton < Button + size: 20 20 + margin-right: 28 + margin-bottom: 4 + anchors.right: parent.right + anchors.bottom: parent.bottom + icon-source: /images/game/minimap/floor_down + icon-clip: 0 32 16 16 + $pressed: + icon-clip: 0 0 16 16 + $hover !pressed: + icon-clip: 0 16 16 16 + +MinimapZoomInButton < Button + text: + + size: 20 20 + margin-right: 4 + margin-bottom: 28 + anchors.right: parent.right + anchors.bottom: parent.bottom + //icon-source: /images/game/minimap/zoom_in + +MinimapZoomOutButton < Button + text: - + size: 20 20 + margin-right: 4 + margin-bottom: 4 + anchors.right: parent.right + anchors.bottom: parent.bottom + //icon-source: /images/game/minimap/zoom_out + +MinimapResetButton < Button + !text: tr('Center') + size: 44 20 + anchors.left: parent.left + anchors.top: parent.top + margin: 4 + +Minimap < UIMinimap + draggable: true + focusable: false + cross: true + color: black + + MinimapFloorUpButton + id: floorUpWidget + @onClick: self:getParent():floorUp(1) + + MinimapFloorDownButton + id: floorDownWidget + @onClick: self:getParent():floorDown(1) + + MinimapZoomInButton + id: zoomInWidget + @onClick: self:getParent():zoomIn() + + MinimapZoomOutButton + id: zoomOutWidget + @onClick: self:getParent():zoomOut() + + MinimapResetButton + id: resetWidget + @onClick: self:getParent():reset() + + +// Minimap Flag Create Window + + +MinimapFlagCheckBox < CheckBox + size: 15 15 + margin-left: 2 + image-source: /images/game/minimap/flagcheckbox + image-size: 15 15 + image-border: 3 + icon-source: /images/game/minimap/mapflags + icon-size: 11 11 + icon-offset: 2 4 + anchors.left: prev.right + anchors.top: prev.top + $!checked: + image-clip: 26 0 26 26 + $hover !checked: + image-clip: 78 0 26 26 + $checked: + image-clip: 0 0 26 26 + $hover checked: + image-clip: 52 0 26 26 + +MinimapFlagWindow < MainWindow + !text: tr('Create Map Mark') + size: 196 185 + + Label + !text: tr('Position') .. ':' + text-auto-resize: true + anchors.top: parent.top + anchors.left: parent.left + margin-top: 2 + + Label + id: position + text-auto-resize: true + anchors.top: parent.top + anchors.right: parent.right + margin-top: 2 + + Label + !text: tr('Description') .. ':' + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 7 + + TextEdit + id: description + margin-top: 3 + anchors.left: parent.left + anchors.top: prev.bottom + anchors.right: parent.right + + MinimapFlagCheckBox + id: flag0 + icon-source: /images/game/minimap/flag0 + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 6 + margin-left: 0 + + MinimapFlagCheckBox + id: flag1 + icon-source: /images/game/minimap/flag1 + + MinimapFlagCheckBox + id: flag2 + icon-source: /images/game/minimap/flag2 + + MinimapFlagCheckBox + id: flag3 + icon-source: /images/game/minimap/flag3 + + MinimapFlagCheckBox + id: flag4 + icon-source: /images/game/minimap/flag4 + + MinimapFlagCheckBox + id: flag5 + icon-source: /images/game/minimap/flag5 + + MinimapFlagCheckBox + id: flag6 + icon-source: /images/game/minimap/flag6 + + MinimapFlagCheckBox + id: flag7 + icon-source: /images/game/minimap/flag7 + + MinimapFlagCheckBox + id: flag8 + icon-source: /images/game/minimap/flag8 + + MinimapFlagCheckBox + id: flag9 + icon-source: /images/game/minimap/flag9 + + MinimapFlagCheckBox + id: flag10 + icon-source: /images/game/minimap/flag10 + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 6 + margin-left: 0 + + MinimapFlagCheckBox + id: flag11 + icon-source: /images/game/minimap/flag11 + + MinimapFlagCheckBox + id: flag12 + icon-source: /images/game/minimap/flag12 + + MinimapFlagCheckBox + id: flag13 + icon-source: /images/game/minimap/flag13 + + MinimapFlagCheckBox + id: flag14 + icon-source: /images/game/minimap/flag14 + + MinimapFlagCheckBox + id: flag15 + icon-source: /images/game/minimap/flag15 + + MinimapFlagCheckBox + id: flag16 + icon-source: /images/game/minimap/flag16 + + MinimapFlagCheckBox + id: flag17 + icon-source: /images/game/minimap/flag17 + + MinimapFlagCheckBox + id: flag18 + icon-source: /images/game/minimap/flag18 + + MinimapFlagCheckBox + id: flag19 + icon-source: /images/game/minimap/flag19 + + Button + id: okButton + !text: tr('Ok') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + + Button + id: cancelButton + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + +MinimapWindow < HeadlessMiniWindow + height: 150 + &forceOpen: true + + MiniWindowContents + margin: 5 + + Minimap + id: minimap + anchors.fill: parent + + ResizeBorder + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + enabled: true \ No newline at end of file diff --git a/800OTClient/layouts/retro/styles/40-outfitwindow.otui b/800OTClient/layouts/retro/styles/40-outfitwindow.otui new file mode 100644 index 0000000..9bc120e --- /dev/null +++ b/800OTClient/layouts/retro/styles/40-outfitwindow.otui @@ -0,0 +1,467 @@ +PartCheckBoxes < Panel + height: 18 + + ButtonBox + id: head + font: cipsoftFont + !text: tr('Head') + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + checked: true + width: 62 + height: 18 + + ButtonBox + id: primary + font: cipsoftFont + !text: tr('Primary') + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + width: 62 + height: 18 + + ButtonBox + id: secondary + font: cipsoftFont + !text: tr('Secondary') + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + width: 62 + height: 18 + + ButtonBox + id: detail + font: cipsoftFont + !text: tr('Detail') + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + width: 62 + height: 18 + + ButtonBox + id: randomizeButton + font: cipsoftFont + !text: tr('Randomize') + !tooltip: tr('Randomize characters outfit') + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + width: 62 + height: 18 + @onClick: modules.game_outfit.randomize() + +AppearanceCategory < Panel + height: 20 + + $!first: + margin-top: 2 + + CheckBox + id: checkBox + image-source: /images/ui/checkbox_round + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + margin-left: 5 + width: 90 + text: Outfit: + @onClick: modules.game_outfit.onOptionChange(self:getParent():getId(), self:isChecked(), self) + + FlatLabel + id: description + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + anchors.right: parent.right + margin-right: 5 + text-align: center + text: - + +WindowPanel < Panel + image-source: /images/ui/window + padding-top: 3 + padding-left: 7 + padding-bottom: 7 + padding-right: 7 + image-border: 4 + image-border-top: 17 + +OptionsCheckBox < Panel + image-source: /images/ui/panel_flat + image-border: 1 + padding: 2 + padding-left: 7 + height: 22 + + CheckBox + id: check + anchors.centerIn: parent + anchors.left: parent.left + anchors.right: parent.right + text-align: left + @onCheckChange: modules.game_outfit.onOptionChange(self:getParent():getId(), self:isChecked(), self) + + $!first: + margin-top: 3 + +PreviewCreaturePanel < FlatPanel + padding: 3 + + Button + id: rotateLeft + anchors.left: parent.left + anchors.bottom: parent.bottom + size: 20 20 + text: < + @onClick: modules.game_outfit.rotatePreview(self:getId()) + + Button + id: rotateRight + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 20 20 + text: > + @onClick: modules.game_outfit.rotatePreview(self:getId()) + + UICreature + id: creature + size: 100 100 + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + +ConfigurePanel < WindowPanel + width: 150 + padding: 3 + + Label + id: title + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + font: verdana-11px-rounded + text: Configure + + Panel + id: options + anchors.fill: parent + anchors.top: prev.bottom + margin-top: 1 + padding: 5 + layout: verticalBox + +SmallPreviewTile < UICheckBox + padding: 5 + image-source: /images/ui/button + @onClick: modules.game_outfit.onElementSelect(self) + image-clip: 0 0 22 23 + image-border: 3 + + $pressed: + image-clip: 0 46 22 23 + + UICreature + id: creature + size: 60 60 + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + phantom: true + creature-fixed-size: true + + UIWidget + id: item + anchors.fill: prev + phantom: true + + Label + id: title + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + text-align: center + font: verdana-11px-rounded + + $checked: + border-width: 1 + border-color: #ffffff + + $!checked: + border-width: 0 + +LargePreviewTile < UICheckBox + padding: 15 + padding-bottom: 2 + image-source: /images/ui/button + @onClick: modules.game_outfit.onElementSelect(self) + image-clip: 0 0 22 23 + image-border: 3 + + $pressed: + image-clip: 0 46 22 23 + + UICreature + id: outfit + size: 60 60 + anchors.left: parent.left + margin-left: 10 + anchors.top: parent.top + phantom: true + + UICreature + id: mount + size: 60 60 + anchors.right: parent.right + margin-right: 10 + anchors.top: parent.top + phantom: true + + Label + id: title + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + text-align: center + font: verdana-11px-rounded + + $checked: + border-width: 1 + border-color: #ffffff + + $!checked: + border-width: 0 + +FilterPanel < WindowPanel + size: 242 47 + + Label + id: title + anchors.left: parent.left + anchors.right: parent.right + text-align: center + anchors.top: parent.top + font: verdana-11px-rounded + text: Filter outfits + + Button + id: clear + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 20 20 + text: X + @onClick: modules.game_outfit.clearFilterText() + + TextEdit + id: filterWindow + anchors.right: prev.left + anchors.left: parent.left + anchors.bottom: parent.bottom + height: 20 + placeholder: Type to search + @onTextChange: modules.game_outfit.onFilterList(self:getText()) + +PresetPanel < WindowPanel + size: 242 47 + padding-left: 2 + padding-bottom: 2 + + Label + id: title + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + font: verdana-11px-rounded + text: Manage Presets + + Button + id: new + size: 45 18 + font: cipsoftFont + text: New + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-bottom: 6 + margin-left: 25 + @onClick: modules.game_outfit.onPresetButtonPress(self:getId()) + + Button + id: rename + size: 45 18 + font: cipsoftFont + text: Rename + anchors.left: prev.right + margin-left: 3 + anchors.verticalCenter: prev.verticalCenter + @onClick: modules.game_outfit.onPresetButtonPress(self:getId()) + + + Button + id: save + size: 45 18 + font: cipsoftFont + text: Save + anchors.left: prev.right + margin-left: 3 + anchors.verticalCenter: prev.verticalCenter + @onClick: modules.game_outfit.onPresetButtonPress(self:getId()) + + Button + id: delete + size: 45 18 + font: cipsoftFont + text: Delete + anchors.left: prev.right + margin-left: 3 + anchors.verticalCenter: prev.verticalCenter + @onClick: modules.game_outfit.onPresetButtonPress(self:getId()) + +PreviewPanel < WindowPanel + size: 477 205 + + Label + id: title + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + font: verdana-11px-rounded + text: Preview Selection + + FlatPanel + id: options + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + margin: 3 + margin-top: 19 + padding: 5 + width: 120 + layout: verticalBox + + Label + font: verdana-11px-rounded + text-align: center + text: Show: + + PreviewCreaturePanel + id: creaturePanel + margin: 3 + margin-top: 19 + anchors.fill: parent + anchors.left: prev.right + +AppearancePanel < WindowPanel + layout: + type: verticalBox + fit-children: true + + Panel + id: categories + margin-top: 20 + layout: + type: verticalBox + fit-children: true + + PartCheckBoxes + id: parts + margin-top: 10 + + FlatPanel + id: colorBoxPanel + margin-top: 2 + padding: 2 + layout: + type: grid + cell-size: 14 14 + cell-spacing: 2 + num-columns: 19 + num-lines: 7 + fit-children: true + +ListBox < ScrollableFlatPanel + width: 242 + padding-top: 6 + padding-left: 6 + padding-bottom: 6 + layout: + type: grid + num-columns: 2 + cell-size: 106 100 + cell-spacing: 6 + flow: true + +OutfitWindow < MainWindow + size: 755 519 + padding-top: 27 + !text: tr('Customize Character') + + FilterPanel + id: search + anchors.top: parent.top + anchors.right: parent.right + + PresetPanel + id: preset + anchors.fill: prev + visible: false + + ListBox + id: list + anchors.top: prev.bottom + margin-top: 5 + anchors.right: parent.right + anchors.bottom: bottomSep.top + margin-bottom: 5 + vertical-scrollbar: scrollBar + + VerticalScrollBar + id: scrollBar + anchors.top: list.top + anchors.bottom: list.bottom + anchors.right: list.right + step: 14 + pixels-scroll: true + + PreviewPanel + id: preview + anchors.top: parent.top + anchors.left: parent.left + + ConfigurePanel + id: config + anchors.top: prev.bottom + margin-top: 5 + anchors.bottom: bottomSep.top + margin-bottom: 5 + anchors.left: parent.left + + AppearancePanel + id: appearance + anchors.left: prev.right + anchors.top: preview.bottom + anchors.right: list.left + margin: 5 + + Label + anchors.top: prev.top + margin-top: 3 + anchors.horizontalCenter: prev.horizontalCenter + font: verdana-11px-rounded + text: Change Appearance + + HorizontalSeparator + id: bottomSep + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: closeButton.top + margin-bottom: 8 + + Button + id: closeButton + !text: tr('Cancel') + font: cipsoftFont + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 45 21 + @onClick: modules.game_outfit.destroy() + + Button + id: okButton + !text: tr('Ok') + font: cipsoftFont + anchors.right: prev.left + margin-right: 7 + anchors.bottom: parent.bottom + size: 45 21 + @onClick: modules.game_outfit.accept() \ No newline at end of file diff --git a/800OTClient/mods/game_healthbars/healthbars.lua b/800OTClient/mods/game_healthbars/healthbars.lua new file mode 100644 index 0000000..017a860 --- /dev/null +++ b/800OTClient/mods/game_healthbars/healthbars.lua @@ -0,0 +1,7 @@ +function init() + g_healthBars.addHealthBackground("/images/bars/health1", -2, -2, 0, 2, 4) + g_healthBars.addManaBackground("/images/bars/mana1", -2, -2, 0, 2, 4) +end + +function terminate() +end diff --git a/800OTClient/mods/game_healthbars/healthbars.otmod b/800OTClient/mods/game_healthbars/healthbars.otmod new file mode 100644 index 0000000..3ecb49d --- /dev/null +++ b/800OTClient/mods/game_healthbars/healthbars.otmod @@ -0,0 +1,10 @@ +Module + name: game_healthbars + description: Load health and mana bars + author: Oen44 + website: http://otclient.ovh + scripts: [ healthbars ] + autoload: false + sandboxed: true + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/client/client.lua b/800OTClient/modules/client/client.lua new file mode 100644 index 0000000..7dfafb2 --- /dev/null +++ b/800OTClient/modules/client/client.lua @@ -0,0 +1,125 @@ +local musicFilename = "/sounds/startup" +local musicChannel = nil + +function setMusic(filename) + musicFilename = filename + + if not g_game.isOnline() and musicChannel ~= nil then + musicChannel:stop() + musicChannel:enqueue(musicFilename, 3) + end +end + +function reloadScripts() + if g_game.getFeature(GameNoDebug) then + return + end + + g_textures.clearCache() + g_modules.reloadModules() + + local script = '/' .. g_app.getCompactName() .. 'rc.lua' + if g_resources.fileExists(script) then + dofile(script) + end + + local message = tr('All modules and scripts were reloaded.') + + modules.game_textmessage.displayGameMessage(message) + print(message) +end + +function startup() + if g_sounds ~= nil then + musicChannel = g_sounds.getChannel(1) + end + + G.UUID = g_settings.getString('report-uuid') + if not G.UUID or #G.UUID ~= 36 then + G.UUID = g_crypt.genUUID() + g_settings.set('report-uuid', G.UUID) + end + + -- Play startup music (The Silver Tree, by Mattias Westlund) + --musicChannel:enqueue(musicFilename, 3) + connect(g_game, { onGameStart = function() if musicChannel ~= nil then musicChannel:stop(3) end end }) + connect(g_game, { onGameEnd = function() + if g_sounds ~= nil then + g_sounds.stopAll() + --musicChannel:enqueue(musicFilename, 3) + end + end }) +end + +function init() + connect(g_app, { onRun = startup, + onExit = exit }) + connect(g_game, { onGameStart = onGameStart, + onGameEnd = onGameEnd }) + + if g_sounds ~= nil then + --g_sounds.preload(musicFilename) + end + + if not Updater then + if g_resources.getLayout() == "mobile" then + g_window.setMinimumSize({ width = 640, height = 360 }) + else + g_window.setMinimumSize({ width = 800, height = 640 }) + end + + -- window size + local size = { width = 1024, height = 600 } + size = g_settings.getSize('window-size', size) + g_window.resize(size) + + -- window position, default is the screen center + local displaySize = g_window.getDisplaySize() + local defaultPos = { x = (displaySize.width - size.width)/2, + y = (displaySize.height - size.height)/2 } + local pos = g_settings.getPoint('window-pos', defaultPos) + pos.x = math.max(pos.x, 0) + pos.y = math.max(pos.y, 0) + g_window.move(pos) + + -- window maximized? + local maximized = g_settings.getBoolean('window-maximized', false) + if maximized then g_window.maximize() end + end + + g_window.setTitle(g_app.getName()) + g_window.setIcon('/images/clienticon') + + g_keyboard.bindKeyDown('Ctrl+Shift+R', reloadScripts) + + -- generate machine uuid, this is a security measure for storing passwords + if not g_crypt.setMachineUUID(g_settings.get('uuid')) then + g_settings.set('uuid', g_crypt.getMachineUUID()) + g_settings.save() + end +end + +function terminate() + disconnect(g_app, { onRun = startup, + onExit = exit }) + disconnect(g_game, { onGameStart = onGameStart, + onGameEnd = onGameEnd }) + -- save window configs + g_settings.set('window-size', g_window.getUnmaximizedSize()) + g_settings.set('window-pos', g_window.getUnmaximizedPos()) + g_settings.set('window-maximized', g_window.isMaximized()) +end + +function exit() + g_logger.info("Exiting application..") +end + +function onGameStart() + local player = g_game.getLocalPlayer() + if not player then return end + g_window.setTitle(g_app.getName() .. " - " .. player:getName()) +end + +function onGameEnd() + g_window.setTitle(g_app.getName()) +end diff --git a/800OTClient/modules/client/client.otmod b/800OTClient/modules/client/client.otmod new file mode 100644 index 0000000..f633b64 --- /dev/null +++ b/800OTClient/modules/client/client.otmod @@ -0,0 +1,23 @@ +Module + name: client + description: Initialize the client and setups its main window + author: edubart + website: https://github.com/edubart/otclient + reloadable: false + sandboxed: true + scripts: [ client ] + @onLoad: init() + @onUnload: terminate() + + load-later: + - client_styles + - client_locales + - client_topmenu + - client_background + - client_textedit + - client_options + - client_entergame + - client_terminal + - client_stats + - client_feedback + - client_mobile \ No newline at end of file diff --git a/800OTClient/modules/client_background/background.lua b/800OTClient/modules/client_background/background.lua new file mode 100644 index 0000000..657424f --- /dev/null +++ b/800OTClient/modules/client_background/background.lua @@ -0,0 +1,49 @@ +-- private variables +local background +local clientVersionLabel + +-- public functions +function init() + background = g_ui.displayUI('background') + background:lower() + + clientVersionLabel = background:getChildById('clientVersionLabel') + clientVersionLabel:setText('OTClientV8 ' .. g_app.getVersion() .. '\nrev ' .. g_app.getBuildRevision() .. '\nMade by:\n' .. g_app.getAuthor() .. "") + + if not g_game.isOnline() then + addEvent(function() g_effects.fadeIn(clientVersionLabel, 1500) end) + end + + connect(g_game, { onGameStart = hide }) + connect(g_game, { onGameEnd = show }) +end + +function terminate() + disconnect(g_game, { onGameStart = hide }) + disconnect(g_game, { onGameEnd = show }) + + g_effects.cancelFade(background:getChildById('clientVersionLabel')) + background:destroy() + + Background = nil +end + +function hide() + background:hide() +end + +function show() + background:show() +end + +function hideVersionLabel() + background:getChildById('clientVersionLabel'):hide() +end + +function setVersionText(text) + clientVersionLabel:setText(text) +end + +function getBackground() + return background +end \ No newline at end of file diff --git a/800OTClient/modules/client_background/background.otmod b/800OTClient/modules/client_background/background.otmod new file mode 100644 index 0000000..8fb34cb --- /dev/null +++ b/800OTClient/modules/client_background/background.otmod @@ -0,0 +1,9 @@ +Module + name: client_background + description: Handles the background of the login screen + author: edubart + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ background ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/client_background/background.otui b/800OTClient/modules/client_background/background.otui new file mode 100644 index 0000000..c90fa1c --- /dev/null +++ b/800OTClient/modules/client_background/background.otui @@ -0,0 +1,21 @@ +UIWidget + id: background + anchors.fill: parent + focusable: false + image-source: /images/background + image-smooth: true + image-fixed-ratio: true + margin-top: 1 + + UILabel + id: clientVersionLabel + background-color: #00000099 + anchors.right: parent.right + anchors.bottom: parent.bottom + text-align: center + text-auto-resize: false + width: 220 + height: 90 + padding: 2 + color: #ffffff + font: terminus-14px-bold \ No newline at end of file diff --git a/800OTClient/modules/client_entergame/characterlist.lua b/800OTClient/modules/client_entergame/characterlist.lua new file mode 100644 index 0000000..ef5ec74 --- /dev/null +++ b/800OTClient/modules/client_entergame/characterlist.lua @@ -0,0 +1,422 @@ +CharacterList = { } + +-- private variables +local charactersWindow +local loadBox +local characterList +local errorBox +local waitingWindow +local autoReconnectButton +local updateWaitEvent +local resendWaitEvent +local loginEvent +local autoReconnectEvent +local lastLogout = 0 + +-- private functions +local function tryLogin(charInfo, tries) + tries = tries or 1 + + if tries > 50 then + return + end + + if g_game.isOnline() then + if tries == 1 then + g_game.safeLogout() + end + loginEvent = scheduleEvent(function() tryLogin(charInfo, tries+1) end, 100) + return + end + + CharacterList.hide() + g_game.loginWorld(G.account, G.password, charInfo.worldName, charInfo.worldHost, charInfo.worldPort, charInfo.characterName, G.authenticatorToken, G.sessionKey) + g_logger.info("Login to " .. charInfo.worldHost .. ":" .. charInfo.worldPort) + loadBox = displayCancelBox(tr('Please wait'), tr('Connecting to game server...')) + connect(loadBox, { onCancel = function() + loadBox = nil + g_game.cancelLogin() + CharacterList.show() + end }) + + -- save last used character + g_settings.set('last-used-character', charInfo.characterName) + g_settings.set('last-used-world', charInfo.worldName) +end + +local function updateWait(timeStart, timeEnd) + if waitingWindow then + local time = g_clock.seconds() + if time <= timeEnd then + local percent = ((time - timeStart) / (timeEnd - timeStart)) * 100 + local timeStr = string.format("%.0f", timeEnd - time) + + local progressBar = waitingWindow:getChildById('progressBar') + progressBar:setPercent(percent) + + local label = waitingWindow:getChildById('timeLabel') + label:setText(tr('Trying to reconnect in %s seconds.', timeStr)) + + updateWaitEvent = scheduleEvent(function() updateWait(timeStart, timeEnd) end, 1000 * progressBar:getPercentPixels() / 100 * (timeEnd - timeStart)) + return true + end + end + + if updateWaitEvent then + updateWaitEvent:cancel() + updateWaitEvent = nil + end +end + +local function resendWait() + if waitingWindow then + waitingWindow:destroy() + waitingWindow = nil + + if updateWaitEvent then + updateWaitEvent:cancel() + updateWaitEvent = nil + end + + if charactersWindow then + local selected = characterList:getFocusedChild() + if selected then + local charInfo = { worldHost = selected.worldHost, + worldPort = selected.worldPort, + worldName = selected.worldName, + characterName = selected.characterName } + tryLogin(charInfo) + end + end + end +end + +local function onLoginWait(message, time) + CharacterList.destroyLoadBox() + + waitingWindow = g_ui.displayUI('waitinglist') + + local label = waitingWindow:getChildById('infoLabel') + label:setText(message) + + updateWaitEvent = scheduleEvent(function() updateWait(g_clock.seconds(), g_clock.seconds() + time) end, 0) + resendWaitEvent = scheduleEvent(resendWait, time * 1000) +end + +function onGameLoginError(message) + CharacterList.destroyLoadBox() + errorBox = displayErrorBox(tr("Login Error"), message) + errorBox.onOk = function() + errorBox = nil + CharacterList.showAgain() + end + scheduleAutoReconnect() +end + +function onGameLoginToken(unknown) + CharacterList.destroyLoadBox() + -- TODO: make it possible to enter a new token here / prompt token + errorBox = displayErrorBox(tr("Two-Factor Authentification"), 'A new authentification token is required.\nPlease login again.') + errorBox.onOk = function() + errorBox = nil + EnterGame.show() + end +end + +function onGameConnectionError(message, code) + CharacterList.destroyLoadBox() + if (not g_game.isOnline() or code ~= 2) and not errorBox then -- code 2 is normal disconnect, end of file + local text = translateNetworkError(code, g_game.getProtocolGame() and g_game.getProtocolGame():isConnecting(), message) + errorBox = displayErrorBox(tr("Connection Error"), text) + errorBox.onOk = function() + errorBox = nil + CharacterList.showAgain() + end + end + scheduleAutoReconnect() +end + +function onGameUpdateNeeded(signature) + CharacterList.destroyLoadBox() + errorBox = displayErrorBox(tr("Update needed"), tr('Enter with your account again to update your client.')) + errorBox.onOk = function() + errorBox = nil + CharacterList.showAgain() + end +end + +function onGameEnd() + scheduleAutoReconnect() + CharacterList.showAgain() +end + +function onLogout() + lastLogout = g_clock.millis() +end + +function scheduleAutoReconnect() + if lastLogout + 2000 > g_clock.millis() then + return + end + if autoReconnectEvent then + removeEvent(autoReconnectEvent) + end + autoReconnectEvent = scheduleEvent(executeAutoReconnect, 2500) +end + +function executeAutoReconnect() + if not autoReconnectButton or not autoReconnectButton:isOn() or g_game.isOnline() then + return + end + if errorBox then + errorBox:destroy() + errorBox = nil + end + CharacterList.doLogin() +end + +-- public functions +function CharacterList.init() + if USE_NEW_ENERGAME then return end + connect(g_game, { onLoginError = onGameLoginError }) + connect(g_game, { onLoginToken = onGameLoginToken }) + connect(g_game, { onUpdateNeeded = onGameUpdateNeeded }) + connect(g_game, { onConnectionError = onGameConnectionError }) + connect(g_game, { onGameStart = CharacterList.destroyLoadBox }) + connect(g_game, { onLoginWait = onLoginWait }) + connect(g_game, { onGameEnd = onGameEnd }) + connect(g_game, { onLogout = onLogout }) + + if G.characters then + CharacterList.create(G.characters, G.characterAccount) + end +end + +function CharacterList.terminate() + if USE_NEW_ENERGAME then return end + disconnect(g_game, { onLoginError = onGameLoginError }) + disconnect(g_game, { onLoginToken = onGameLoginToken }) + disconnect(g_game, { onUpdateNeeded = onGameUpdateNeeded }) + disconnect(g_game, { onConnectionError = onGameConnectionError }) + disconnect(g_game, { onGameStart = CharacterList.destroyLoadBox }) + disconnect(g_game, { onLoginWait = onLoginWait }) + disconnect(g_game, { onGameEnd = onGameEnd }) + disconnect(g_game, { onLogout = onLogout }) + + if charactersWindow then + characterList = nil + charactersWindow:destroy() + charactersWindow = nil + end + + if loadBox then + g_game.cancelLogin() + loadBox:destroy() + loadBox = nil + end + + if waitingWindow then + waitingWindow:destroy() + waitingWindow = nil + end + + if updateWaitEvent then + removeEvent(updateWaitEvent) + updateWaitEvent = nil + end + + if resendWaitEvent then + removeEvent(resendWaitEvent) + resendWaitEvent = nil + end + + if loginEvent then + removeEvent(loginEvent) + loginEvent = nil + end + + CharacterList = nil +end + +function CharacterList.create(characters, account, otui) + if not otui then otui = 'characterlist' end + if charactersWindow then + charactersWindow:destroy() + end + + charactersWindow = g_ui.displayUI(otui) + characterList = charactersWindow:getChildById('characters') + autoReconnectButton = charactersWindow:getChildById('autoReconnect') + + -- characters + G.characters = characters + G.characterAccount = account + + characterList:destroyChildren() + local accountStatusLabel = charactersWindow:getChildById('accountStatusLabel') + local focusLabel + for i,characterInfo in ipairs(characters) do + local widget = g_ui.createWidget('CharacterWidget', characterList) + for key,value in pairs(characterInfo) do + local subWidget = widget:getChildById(key) + if subWidget then + if key == 'outfit' then -- it's an exception + subWidget:setOutfit(value) + else + local text = value + if subWidget.baseText and subWidget.baseTranslate then + text = tr(subWidget.baseText, text) + elseif subWidget.baseText then + text = string.format(subWidget.baseText, text) + end + subWidget:setText(text) + end + end + end + + -- these are used by login + widget.characterName = characterInfo.name + widget.worldName = characterInfo.worldName + widget.worldHost = characterInfo.worldIp + widget.worldPort = characterInfo.worldPort + + connect(widget, { onDoubleClick = function () CharacterList.doLogin() return true end } ) + + if i == 1 or (g_settings.get('last-used-character') == widget.characterName and g_settings.get('last-used-world') == widget.worldName) then + focusLabel = widget + end + end + + if focusLabel then + characterList:focusChild(focusLabel, KeyboardFocusReason) + addEvent(function() characterList:ensureChildVisible(focusLabel) end) + end + + characterList.onChildFocusChange = function() + removeEvent(autoReconnectEvent) + autoReconnectEvent = nil + end + + -- account + local status = '' + if account.status == AccountStatus.Frozen then + status = tr(' (Frozen)') + elseif account.status == AccountStatus.Suspended then + status = tr(' (Suspended)') + end + + if account.subStatus == SubscriptionStatus.Free and account.premDays < 1 then + accountStatusLabel:setText(('%s%s'):format(tr('Free Account'), status)) + else + if account.premDays == 0 or account.premDays == 65535 then + accountStatusLabel:setText(('%s%s'):format(tr('Gratis Premium Account'), status)) + else + accountStatusLabel:setText(('%s%s'):format(tr('Premium Account (%s) days left', account.premDays), status)) + end + end + + if account.premDays > 0 and account.premDays <= 7 then + accountStatusLabel:setOn(true) + else + accountStatusLabel:setOn(false) + end + + autoReconnectButton.onClick = function(widget) + local autoReconnect = not g_settings.getBoolean('autoReconnect', true) + autoReconnectButton:setOn(autoReconnect) + g_settings.set('autoReconnect', autoReconnect) + end +end + +function CharacterList.destroy() + CharacterList.hide(true) + + if charactersWindow then + characterList = nil + charactersWindow:destroy() + charactersWindow = nil + end +end + +function CharacterList.show() + if loadBox or errorBox or not charactersWindow then return end + charactersWindow:show() + charactersWindow:raise() + charactersWindow:focus() + + local autoReconnect = g_settings.getBoolean('autoReconnect', true) + autoReconnectButton:setOn(autoReconnect) +end + +function CharacterList.hide(showLogin) + removeEvent(autoReconnectEvent) + autoReconnectEvent = nil + + showLogin = showLogin or false + charactersWindow:hide() + + if showLogin and EnterGame and not g_game.isOnline() then + EnterGame.show() + end +end + +function CharacterList.showAgain() + if characterList and characterList:hasChildren() then + CharacterList.show() + end +end + +function CharacterList.isVisible() + if charactersWindow and charactersWindow:isVisible() then + return true + end + return false +end + +function CharacterList.doLogin() + removeEvent(autoReconnectEvent) + autoReconnectEvent = nil + + local selected = characterList:getFocusedChild() + if selected then + local charInfo = { worldHost = selected.worldHost, + worldPort = selected.worldPort, + worldName = selected.worldName, + characterName = selected.characterName } + charactersWindow:hide() + if loginEvent then + removeEvent(loginEvent) + loginEvent = nil + end + tryLogin(charInfo) + else + displayErrorBox(tr('Error'), tr('You must select a character to login!')) + end +end + +function CharacterList.destroyLoadBox() + if loadBox then + loadBox:destroy() + loadBox = nil + end +end + +function CharacterList.cancelWait() + if waitingWindow then + waitingWindow:destroy() + waitingWindow = nil + end + + if updateWaitEvent then + removeEvent(updateWaitEvent) + updateWaitEvent = nil + end + + if resendWaitEvent then + removeEvent(resendWaitEvent) + resendWaitEvent = nil + end + + CharacterList.destroyLoadBox() + CharacterList.showAgain() +end diff --git a/800OTClient/modules/client_entergame/characterlist.otui b/800OTClient/modules/client_entergame/characterlist.otui new file mode 100644 index 0000000..135dd5e --- /dev/null +++ b/800OTClient/modules/client_entergame/characterlist.otui @@ -0,0 +1,133 @@ +CharacterWidget < UIWidget + height: 14 + background-color: alpha + &updateOnStates: | + function(self) + local children = self:getChildren() + for i=1,#children do + children[i]:setOn(self:isFocused()) + end + end + @onFocusChange: self:updateOnStates() + @onSetup: self:updateOnStates() + + $focus: + background-color: #ffffff22 + + Label + id: name + color: #bbbbbb + anchors.top: parent.top + anchors.left: parent.left + font: verdana-11px-monochrome + text-auto-resize: true + background-color: alpha + text-offset: 2 0 + + $on: + color: #ffffff + + Label + id: worldName + color: #bbbbbb + anchors.top: parent.top + anchors.right: parent.right + margin-right: 5 + font: verdana-11px-monochrome + text-auto-resize: true + background-color: alpha + &baseText: '(%s)' + + $on: + color: #ffffff + +StaticMainWindow + id: charactersWindow + !text: tr('Character List') + visible: false + size: 350 400 + $mobile: + size: 350 280 + @onEnter: CharacterList.doLogin() + @onEscape: CharacterList.hide(true) + @onSetup: | + g_keyboard.bindKeyPress('Up', function() self:getChildById('characters'):focusPreviousChild(KeyboardFocusReason) end, self) + g_keyboard.bindKeyPress('Down', function() self:getChildById('characters'):focusNextChild(KeyboardFocusReason) end, self) + + TextList + id: characters + background-color: #565656 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: characterListScrollBar.left + anchors.bottom: accountStatusCaption.top + margin-bottom: 5 + padding: 1 + focusable: false + vertical-scrollbar: characterListScrollBar + auto-focus: first + + VerticalScrollBar + id: characterListScrollBar + anchors.top: parent.top + anchors.bottom: accountStatusCaption.top + anchors.right: parent.right + margin-bottom: 5 + step: 14 + pixels-scroll: true + + Label + id: accountStatusCaption + !text: tr('Account Status') .. ':' + anchors.left: parent.left + anchors.bottom: separator.top + margin-bottom: 5 + + Label + id: accountStatusLabel + !text: tr('Free Account') + anchors.right: parent.right + anchors.bottom: separator.top + margin-bottom: 5 + text-auto-resize: true + + $on: + color: #FF0000 + + HorizontalSeparator + id: separator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + margin-bottom: 10 + + Button + id: autoReconnect + width: 140 + anchors.left: parent.left + anchors.bottom: parent.bottom + + $!on: + image-color: red + !text: tr('Auto reconnect: Off') + + $on: + !text: tr('Auto reconnect: On') + image-color: green + + Button + id: buttonOk + !text: tr('Ok') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + @onClick: CharacterList.doLogin() + + Button + id: buttonCancel + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: CharacterList.hide(true) diff --git a/800OTClient/modules/client_entergame/entergame.lua b/800OTClient/modules/client_entergame/entergame.lua new file mode 100644 index 0000000..afe76f7 --- /dev/null +++ b/800OTClient/modules/client_entergame/entergame.lua @@ -0,0 +1,628 @@ +EnterGame = { } + +-- private variables +local loadBox +local enterGame +local enterGameButton +local logpass +local clientBox +local protocolLogin +local server = nil +local versionsFound = false + +local customServerSelectorPanel +local serverSelectorPanel +local serverSelector +local clientVersionSelector +local serverHostTextEdit +local rememberPasswordBox +local protos = {"740", "760", "772", "792", "800", "810", "854", "860", "870", "910", "961", "1000", "1077", "1090", "1096", "1098", "1099", "1100", "1200", "1220"} + +local checkedByUpdater = {} +local waitingForHttpResults = 0 + +-- private functions +local function onProtocolError(protocol, message, errorCode) + if errorCode then + return EnterGame.onError(message) + end + return EnterGame.onLoginError(message) +end + +local function onSessionKey(protocol, sessionKey) + G.sessionKey = sessionKey +end + +local function onCharacterList(protocol, characters, account, otui) + if rememberPasswordBox:isChecked() then + local account = g_crypt.encrypt(G.account) + local password = g_crypt.encrypt(G.password) + + g_settings.set('account', account) + g_settings.set('password', password) + else + EnterGame.clearAccountFields() + end + + for _, characterInfo in pairs(characters) do + if characterInfo.previewState and characterInfo.previewState ~= PreviewState.Default then + characterInfo.worldName = characterInfo.worldName .. ', Preview' + end + end + + if loadBox then + loadBox:destroy() + loadBox = nil + end + + CharacterList.create(characters, account, otui) + CharacterList.show() + + g_settings.save() +end + +local function onUpdateNeeded(protocol, signature) + return EnterGame.onError(tr('Your client needs updating, try redownloading it.')) +end + +local function onProxyList(protocol, proxies) + for _, proxy in ipairs(proxies) do + g_proxy.addProxy(proxy["host"], proxy["port"], proxy["priority"]) + end +end + +local function parseFeatures(features) + for feature_id, value in pairs(features) do + if value == "1" or value == "true" or value == true then + g_game.enableFeature(feature_id) + else + g_game.disableFeature(feature_id) + end + end +end + +local function validateThings(things) + local incorrectThings = "" + local missingFiles = false + local versionForMissingFiles = 0 + if things ~= nil then + local thingsNode = {} + for thingtype, thingdata in pairs(things) do + thingsNode[thingtype] = thingdata[1] + if not g_resources.fileExists("/things/" .. thingdata[1]) then + incorrectThings = incorrectThings .. "Missing file: " .. thingdata[1] .. "\n" + missingFiles = true + versionForMissingFiles = thingdata[1]:split("/")[1] + else + local localChecksum = g_resources.fileChecksum("/things/" .. thingdata[1]):lower() + if localChecksum ~= thingdata[2]:lower() and #thingdata[2] > 1 then + if g_resources.isLoadedFromArchive() then -- ignore checksum if it's test/debug version + incorrectThings = incorrectThings .. "Invalid checksum of file: " .. thingdata[1] .. " (is " .. localChecksum .. ", should be " .. thingdata[2]:lower() .. ")\n" + end + end + end + end + g_settings.setNode("things", thingsNode) + else + g_settings.setNode("things", {}) + end + if missingFiles then + incorrectThings = incorrectThings .. "\nYou should open data/things and create directory " .. versionForMissingFiles .. + ".\nIn this directory (data/things/" .. versionForMissingFiles .. ") you should put missing\nfiles (Tibia.dat and Tibia.spr/Tibia.cwm) " .. + "from correct Tibia version." + end + return incorrectThings +end + +local function onTibia12HTTPResult(session, playdata) + local characters = {} + local worlds = {} + local account = { + status = 0, + subStatus = 0, + premDays = 0 + } + if session["status"] ~= "active" then + account.status = 1 + end + if session["ispremium"] then + account.subStatus = 1 -- premium + end + if session["premiumuntil"] > g_clock.seconds() then + account.subStatus = math.floor((session["premiumuntil"] - g_clock.seconds()) / 86400) + end + + local things = { + data = {G.clientVersion .. "/Tibia.dat", ""}, + sprites = {G.clientVersion .. "/Tibia.cwm", ""}, + } + + local incorrectThings = validateThings(things) + if #incorrectThings > 0 then + things = { + data = {G.clientVersion .. "/Tibia.dat", ""}, + sprites = {G.clientVersion .. "/Tibia.spr", ""}, + } + incorrectThings = validateThings(things) + end + + if #incorrectThings > 0 then + g_logger.error(incorrectThings) + if Updater and not checkedByUpdater[G.clientVersion] then + checkedByUpdater[G.clientVersion] = true + return Updater.check({ + version = G.clientVersion, + host = G.host + }) + else + return EnterGame.onError(incorrectThings) + end + end + + onSessionKey(nil, session["sessionkey"]) + + for _, world in pairs(playdata["worlds"]) do + worlds[world.id] = { + name = world.name, + port = world.externalportunprotected or world.externalportprotected or world.externaladdress, + address = world.externaladdressunprotected or world.externaladdressprotected or world.externalport + } + end + + for _, character in pairs(playdata["characters"]) do + local world = worlds[character.worldid] + if world then + table.insert(characters, { + name = character.name, + worldName = world.name, + worldIp = world.address, + worldPort = world.port + }) + end + end + + -- proxies + if g_proxy then + g_proxy.clear() + if playdata["proxies"] then + for i, proxy in ipairs(playdata["proxies"]) do + g_proxy.addProxy(proxy["host"], tonumber(proxy["port"]), tonumber(proxy["priority"])) + end + end + end + + g_game.setCustomProtocolVersion(0) + g_game.chooseRsa(G.host) + g_game.setClientVersion(G.clientVersion) + g_game.setProtocolVersion(g_game.getClientProtocolVersion(G.clientVersion)) + g_game.setCustomOs(-1) -- disable + if not g_game.getFeature(GameExtendedOpcode) then + g_game.setCustomOs(5) -- set os to windows if opcodes are disabled + end + + onCharacterList(nil, characters, account, nil) +end + +local function onHTTPResult(data, err) + if waitingForHttpResults == 0 then + return + end + + waitingForHttpResults = waitingForHttpResults - 1 + if err and waitingForHttpResults > 0 then + return -- ignore, wait for other requests + end + + if err then + return EnterGame.onError(err) + end + waitingForHttpResults = 0 + if data['error'] and data['error']:len() > 0 then + return EnterGame.onLoginError(data['error']) + elseif data['errorMessage'] and data['errorMessage']:len() > 0 then + return EnterGame.onLoginError(data['errorMessage']) + end + + if type(data["session"]) == "table" and type(data["playdata"]) == "table" then + return onTibia12HTTPResult(data["session"], data["playdata"]) + end + + local characters = data["characters"] + local account = data["account"] + local session = data["session"] + + local version = data["version"] + local things = data["things"] + local customProtocol = data["customProtocol"] + + local features = data["features"] + local settings = data["settings"] + local rsa = data["rsa"] + local proxies = data["proxies"] + + local incorrectThings = validateThings(things) + if #incorrectThings > 0 then + g_logger.info(incorrectThings) + return EnterGame.onError(incorrectThings) + end + + -- custom protocol + g_game.setCustomProtocolVersion(0) + if customProtocol ~= nil then + customProtocol = tonumber(customProtocol) + if customProtocol ~= nil and customProtocol > 0 then + g_game.setCustomProtocolVersion(customProtocol) + end + end + + -- force player settings + if settings ~= nil then + for option, value in pairs(settings) do + modules.client_options.setOption(option, value, true) + end + end + + -- version + G.clientVersion = version + g_game.setClientVersion(version) + g_game.setProtocolVersion(g_game.getClientProtocolVersion(version)) + g_game.setCustomOs(-1) -- disable + + if rsa ~= nil then + g_game.setRsa(rsa) + end + + if features ~= nil then + parseFeatures(features) + end + + if session ~= nil and session:len() > 0 then + onSessionKey(nil, session) + end + + -- proxies + if g_proxy then + g_proxy.clear() + if proxies then + for i, proxy in ipairs(proxies) do + g_proxy.addProxy(proxy["host"], tonumber(proxy["port"]), tonumber(proxy["priority"])) + end + end + end + + onCharacterList(nil, characters, account, nil) +end + + +-- public functions +function EnterGame.init() + if USE_NEW_ENERGAME then return end + enterGame = g_ui.displayUI('entergame') + if LOGPASS ~= nil then + logpass = g_ui.loadUI('logpass', enterGame:getParent()) + end + + serverSelectorPanel = enterGame:getChildById('serverSelectorPanel') + customServerSelectorPanel = enterGame:getChildById('customServerSelectorPanel') + + serverSelector = serverSelectorPanel:getChildById('serverSelector') + rememberPasswordBox = enterGame:getChildById('rememberPasswordBox') + serverHostTextEdit = customServerSelectorPanel:getChildById('serverHostTextEdit') + clientVersionSelector = customServerSelectorPanel:getChildById('clientVersionSelector') + + if Servers ~= nil then + for name,server in pairs(Servers) do + serverSelector:addOption(name) + end + end + if serverSelector:getOptionsCount() == 0 or ALLOW_CUSTOM_SERVERS then + serverSelector:addOption(tr("Another")) + end + for i,proto in pairs(protos) do + clientVersionSelector:addOption(proto) + end + + if serverSelector:getOptionsCount() == 1 then + enterGame:setHeight(enterGame:getHeight() - serverSelectorPanel:getHeight()) + serverSelectorPanel:setOn(false) + end + + local account = g_crypt.decrypt(g_settings.get('account')) + local password = g_crypt.decrypt(g_settings.get('password')) + local server = g_settings.get('server') + local host = g_settings.get('host') + local clientVersion = g_settings.get('client-version') + + if serverSelector:isOption(server) then + serverSelector:setCurrentOption(server, false) + if Servers == nil or Servers[server] == nil then + serverHostTextEdit:setText(host) + end + clientVersionSelector:setOption(clientVersion) + else + server = "" + host = "" + end + + enterGame:getChildById('accountPasswordTextEdit'):setText(password) + enterGame:getChildById('accountNameTextEdit'):setText(account) + rememberPasswordBox:setChecked(#account > 0) + + g_keyboard.bindKeyDown('Ctrl+G', EnterGame.openWindow) + + if g_game.isOnline() then + return EnterGame.hide() + end + + scheduleEvent(function() + EnterGame.show() + end, 100) +end + +function EnterGame.terminate() + if not enterGame then return end + g_keyboard.unbindKeyDown('Ctrl+G') + + if logpass then + logpass:destroy() + logpass = nil + end + + enterGame:destroy() + if loadBox then + loadBox:destroy() + loadBox = nil + end + if protocolLogin then + protocolLogin:cancelLogin() + protocolLogin = nil + end + EnterGame = nil +end + +function EnterGame.show() + if not enterGame then return end + enterGame:show() + enterGame:raise() + enterGame:focus() + enterGame:getChildById('accountNameTextEdit'):focus() + if logpass then + logpass:show() + logpass:raise() + logpass:focus() + end +end + +function EnterGame.hide() + if not enterGame then return end + enterGame:hide() + if logpass then + logpass:hide() + if modules.logpass then + modules.logpass:hide() + end + end +end + +function EnterGame.openWindow() + if g_game.isOnline() then + CharacterList.show() + elseif not g_game.isLogging() and not CharacterList.isVisible() then + EnterGame.show() + end +end + +function EnterGame.clearAccountFields() + enterGame:getChildById('accountNameTextEdit'):clearText() + enterGame:getChildById('accountPasswordTextEdit'):clearText() + enterGame:getChildById('accountTokenTextEdit'):clearText() + enterGame:getChildById('accountNameTextEdit'):focus() + g_settings.remove('account') + g_settings.remove('password') +end + +function EnterGame.onServerChange() + server = serverSelector:getText() + if server == tr("Another") then + if not customServerSelectorPanel:isOn() then + serverHostTextEdit:setText("") + customServerSelectorPanel:setOn(true) + enterGame:setHeight(enterGame:getHeight() + customServerSelectorPanel:getHeight()) + end + elseif customServerSelectorPanel:isOn() then + enterGame:setHeight(enterGame:getHeight() - customServerSelectorPanel:getHeight()) + customServerSelectorPanel:setOn(false) + end + if Servers and Servers[server] ~= nil then + if type(Servers[server]) == "table" then + serverHostTextEdit:setText(Servers[server][1]) + else + serverHostTextEdit:setText(Servers[server]) + end + end +end + +function EnterGame.doLogin(account, password, token, host) + if g_game.isOnline() then + local errorBox = displayErrorBox(tr('Login Error'), tr('Cannot login while already in game.')) + connect(errorBox, { onOk = EnterGame.show }) + return + end + + G.account = account or enterGame:getChildById('accountNameTextEdit'):getText() + G.password = password or enterGame:getChildById('accountPasswordTextEdit'):getText() + G.authenticatorToken = token or enterGame:getChildById('accountTokenTextEdit'):getText() + G.stayLogged = true + G.server = serverSelector:getText():trim() + G.host = host or serverHostTextEdit:getText() + G.clientVersion = tonumber(clientVersionSelector:getText()) + + if not rememberPasswordBox:isChecked() then + g_settings.set('account', G.account) + g_settings.set('password', G.password) + end + g_settings.set('host', G.host) + g_settings.set('server', G.server) + g_settings.set('client-version', G.clientVersion) + g_settings.save() + + local server_params = G.host:split(":") + if G.host:lower():find("http") ~= nil then + if #server_params >= 4 then + G.host = server_params[1] .. ":" .. server_params[2] .. ":" .. server_params[3] + G.clientVersion = tonumber(server_params[4]) + elseif #server_params >= 3 then + if tostring(tonumber(server_params[3])) == server_params[3] then + G.host = server_params[1] .. ":" .. server_params[2] + G.clientVersion = tonumber(server_params[3]) + end + end + return EnterGame.doLoginHttp() + end + + local server_ip = server_params[1] + local server_port = 7171 + if #server_params >= 2 then + server_port = tonumber(server_params[2]) + end + if #server_params >= 3 then + G.clientVersion = tonumber(server_params[3]) + end + if type(server_ip) ~= 'string' or server_ip:len() <= 3 or not server_port or not G.clientVersion then + return EnterGame.onError("Invalid server, it should be in format IP:PORT or it should be http url to login script") + end + + local things = { + data = {G.clientVersion .. "/Tibia.dat", ""}, + sprites = {G.clientVersion .. "/Tibia.cwm", ""}, + } + + local incorrectThings = validateThings(things) + if #incorrectThings > 0 then + things = { + data = {G.clientVersion .. "/Tibia.dat", ""}, + sprites = {G.clientVersion .. "/Tibia.spr", ""}, + } + incorrectThings = validateThings(things) + end + if #incorrectThings > 0 then + g_logger.error(incorrectThings) + if Updater and not checkedByUpdater[G.clientVersion] then + checkedByUpdater[G.clientVersion] = true + return Updater.check({ + version = G.clientVersion, + host = G.host + }) + else + return EnterGame.onError(incorrectThings) + end + end + + protocolLogin = ProtocolLogin.create() + protocolLogin.onLoginError = onProtocolError + protocolLogin.onSessionKey = onSessionKey + protocolLogin.onCharacterList = onCharacterList + protocolLogin.onUpdateNeeded = onUpdateNeeded + protocolLogin.onProxyList = onProxyList + + EnterGame.hide() + loadBox = displayCancelBox(tr('Please wait'), tr('Connecting to login server...')) + connect(loadBox, { onCancel = function(msgbox) + loadBox = nil + protocolLogin:cancelLogin() + EnterGame.show() + end }) + + if G.clientVersion == 1000 then -- some people don't understand that tibia 10 uses 1100 protocol + G.clientVersion = 1100 + end + -- if you have custom rsa or protocol edit it here + g_game.setClientVersion(G.clientVersion) + g_game.setProtocolVersion(g_game.getClientProtocolVersion(G.clientVersion)) + g_game.setCustomProtocolVersion(0) + g_game.setCustomOs(-1) -- disable + g_game.chooseRsa(G.host) + if #server_params <= 3 and not g_game.getFeature(GameExtendedOpcode) then + g_game.setCustomOs(2) -- set os to windows if opcodes are disabled + end + + -- extra features from init.lua + for i = 4, #server_params do + g_game.enableFeature(tonumber(server_params[i])) + end + + -- proxies + if g_proxy then + g_proxy.clear() + end + + if modules.game_things.isLoaded() then + g_logger.info("Connecting to: " .. server_ip .. ":" .. server_port) + protocolLogin:login(server_ip, server_port, G.account, G.password, G.authenticatorToken, G.stayLogged) + else + loadBox:destroy() + loadBox = nil + EnterGame.show() + end +end + +function EnterGame.doLoginHttp() + if G.host == nil or G.host:len() < 10 then + return EnterGame.onError("Invalid server url: " .. G.host) + end + + loadBox = displayCancelBox(tr('Please wait'), tr('Connecting to login server...')) + connect(loadBox, { onCancel = function(msgbox) + loadBox = nil + EnterGame.show() + end }) + + local data = { + type = "login", + account = G.account, + accountname = G.account, + email = G.account, + password = G.password, + accountpassword = G.password, + token = G.authenticatorToken, + version = APP_VERSION, + uid = G.UUID, + stayloggedin = true + } + + local server = serverSelector:getText() + if Servers and Servers[server] ~= nil then + if type(Servers[server]) == "table" then + local urls = Servers[server] + waitingForHttpResults = #urls + for _, url in ipairs(urls) do + HTTP.postJSON(url, data, onHTTPResult) + end + else + waitingForHttpResults = 1 + HTTP.postJSON(G.host, data, onHTTPResult) + end + end + EnterGame.hide() +end + +function EnterGame.onError(err) + if loadBox then + loadBox:destroy() + loadBox = nil + end + local errorBox = displayErrorBox(tr('Login Error'), err) + errorBox.onOk = EnterGame.show +end + +function EnterGame.onLoginError(err) + if loadBox then + loadBox:destroy() + loadBox = nil + end + local errorBox = displayErrorBox(tr('Login Error'), err) + errorBox.onOk = EnterGame.show + if err:lower():find("invalid") or err:lower():find("not correct") or err:lower():find("or password") then + EnterGame.clearAccountFields() + end +end diff --git a/800OTClient/modules/client_entergame/entergame.otmod b/800OTClient/modules/client_entergame/entergame.otmod new file mode 100644 index 0000000..3b39ae1 --- /dev/null +++ b/800OTClient/modules/client_entergame/entergame.otmod @@ -0,0 +1,12 @@ +Module + name: client_entergame + description: Manages enter game and character list windows + author: edubart & otclient.ovh + website: https://github.com/edubart/otclient + scripts: [ entergame, characterlist ] + @onLoad: EnterGame.init() CharacterList.init() + @onUnload: EnterGame.terminate() CharacterList.terminate() + + load-later: + - game_things + - game_features diff --git a/800OTClient/modules/client_entergame/entergame.otui b/800OTClient/modules/client_entergame/entergame.otui new file mode 100644 index 0000000..04ec0d7 --- /dev/null +++ b/800OTClient/modules/client_entergame/entergame.otui @@ -0,0 +1,186 @@ +EnterGameWindow + id: enterGame + @onEnter: EnterGame.doLogin() + + MenuLabel + !text: tr('Account name') + anchors.left: parent.left + anchors.top: parent.top + text-auto-resize: true + + TextEdit + id: accountNameTextEdit + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 2 + + MenuLabel + !text: tr('Password') + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 8 + text-auto-resize: true + + PasswordTextEdit + id: accountPasswordTextEdit + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 2 + + MenuLabel + !text: tr('Token') + anchors.left: prev.left + anchors.top: prev.bottom + text-auto-resize: true + margin-top: 8 + + TextEdit + id: accountTokenTextEdit + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 2 + + Panel + id: serverSelectorPanel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + height: 52 + on: true + focusable: false + + $on: + visible: true + margin-top: 0 + + $!on: + visible: false + margin-top: -52 + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + margin-top: 10 + + MenuLabel + id: serverLabel + !text: tr('Server') + anchors.left: parent.left + anchors.top: prev.bottom + text-auto-resize: true + margin-top: 5 + + ComboBox + id: serverSelector + anchors.left: prev.left + anchors.right: parent.right + anchors.top: serverLabel.bottom + margin-top: 2 + margin-right: 3 + menu-scroll: true + menu-height: 125 + menu-scroll-step: 25 + text-offset: 5 2 + @onOptionChange: EnterGame.onServerChange() + + Panel + id: customServerSelectorPanel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + height: 52 + on: true + focusable: true + + $on: + visible: true + margin-top: 0 + + $!on: + visible: false + margin-top: -52 + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + margin-top: 8 + + MenuLabel + id: serverLabel + !text: tr('IP:PORT or URL') + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 8 + text-auto-resize: true + + TextEdit + id: serverHostTextEdit + !tooltip: tr('Make sure that your client uses\nthe correct game client version') + anchors.left: parent.left + anchors.top: serverLabel.bottom + margin-top: 2 + width: 150 + + MenuLabel + id: clientLabel + !text: tr('Version') + anchors.left: serverHostTextEdit.right + anchors.top: serverLabel.top + text-auto-resize: true + margin-left: 10 + + ComboBox + id: clientVersionSelector + anchors.top: serverHostTextEdit.top + anchors.bottom: serverHostTextEdit.bottom + anchors.left: prev.left + anchors.right: parent.right + menu-scroll: true + menu-height: 125 + menu-scroll-step: 25 + margin-right: 3 + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 10 + + CheckBox + id: rememberPasswordBox + !text: tr('Remember password') + !tooltip: tr('Remember account and password when starts client') + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 9 + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 9 + + Button + !text: tr('Login') + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 10 + margin-left: 50 + margin-right: 50 + @onClick: EnterGame.doLogin() + + Label + id: serverInfoLabel + font: verdana-11px-rounded + anchors.top: prev.top + anchors.left: parent.left + margin-top: 5 + color: green + text-auto-resize: true \ No newline at end of file diff --git a/800OTClient/modules/client_entergame/logpass.otui b/800OTClient/modules/client_entergame/logpass.otui new file mode 100644 index 0000000..91fe96a --- /dev/null +++ b/800OTClient/modules/client_entergame/logpass.otui @@ -0,0 +1,9 @@ +UIWidget + id: logpass + size: 248 41 + anchors.top: enterGame.bottom + anchors.horizontalCenter: enterGame.horizontalCenter + margin-top: 25 + image-size: 248 41 + image-source: /images/ui/continue_with_logpass.png + @onClick: modules.logpass.show() \ No newline at end of file diff --git a/800OTClient/modules/client_entergame/waitinglist.otui b/800OTClient/modules/client_entergame/waitinglist.otui new file mode 100644 index 0000000..c7b86a6 --- /dev/null +++ b/800OTClient/modules/client_entergame/waitinglist.otui @@ -0,0 +1,44 @@ +MainWindow + id: waitingWindow + !text: tr('Waiting List') + size: 260 180 + @onEscape: CharacterList.cancelWait() + + Label + id: infoLabel + anchors.top: parent.top + anchors.bottom: progressBar.top + anchors.left: parent.left + anchors.right: parent.right + text-wrap: true + + ProgressBar + id: progressBar + height: 15 + background-color: #4444ff + anchors.bottom: timeLabel.top + anchors.left: parent.left + anchors.right: parent.right + margin-bottom: 10 + + Label + id: timeLabel + anchors.bottom: separator.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-bottom: 10 + + HorizontalSeparator + id: separator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + margin-bottom: 10 + + Button + id: buttonCancel + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: CharacterList.cancelWait() \ No newline at end of file diff --git a/800OTClient/modules/client_feedback/feedback.lua b/800OTClient/modules/client_feedback/feedback.lua new file mode 100644 index 0000000..d6b9b0d --- /dev/null +++ b/800OTClient/modules/client_feedback/feedback.lua @@ -0,0 +1,109 @@ +local feedbackWindow +local textEdit +local okButton +local cancelButton +local postId = 0 +local tries = 0 +local replyEvent = nil + +function init() + feedbackWindow = g_ui.displayUI('feedback') + feedbackWindow:hide() + + textEdit = feedbackWindow:getChildById('text') + okButton = feedbackWindow:getChildById('okButton') + cancelButton = feedbackWindow:getChildById('cancelButton') + + okButton.onClick = send + cancelButton.onClick = hide + feedbackWindow.onEscape = hide +end + +function terminate() + feedbackWindow:destroy() + removeEvent(replyEvent) +end + +function show() + if not Services or not Services.feedback or Services.feedback:len() < 4 then + return + end + + feedbackWindow:show() + feedbackWindow:raise() + feedbackWindow:focus() + + textEdit:setMaxLength(8192) + textEdit:setText('') + textEdit:setEditable(true) + textEdit:setCursorVisible(true) + feedbackWindow:focusChild(textEdit, KeyboardFocusReason) + + tries = 0 +end + +function hide() + feedbackWindow:hide() + textEdit:setEditable(false) + textEdit:setCursorVisible(false) +end + +function send() + local text = textEdit:getText() + if text:len() > 1 then + local localPlayer = g_game.getLocalPlayer() + local playerData = nil + if localPlayer ~= nil then + playerData = { + name = localPlayer:getName(), + position = localPlayer:getPosition() + } + end + local details = { + report_delay = sendInterval, + os = g_app.getOs(), + graphics_vendor = g_graphics.getVendor(), + graphics_renderer = g_graphics.getRenderer(), + graphics_version = g_graphics.getVersion(), + fps = g_app.getFps(), + maxFps = g_app.getMaxFps(), + atlas = g_atlas.getStats(), + classic = tostring(g_settings.getBoolean("classicView")), + fullscreen = tostring(g_window.isFullscreen()), + vsync = tostring(g_settings.getBoolean("vsync")), + window_width = g_window.getWidth(), + window_height = g_window.getHeight(), + player_name = g_game.getCharacterName(), + world_name = g_game.getWorldName(), + otserv_host = G.host, + otserv_protocol = g_game.getProtocolVersion(), + otserv_client = g_game.getClientVersion(), + build_version = g_app.getVersion(), + build_revision = g_app.getBuildRevision(), + build_commit = g_app.getBuildCommit(), + build_date = g_app.getBuildDate(), + display_width = g_window.getDisplayWidth(), + display_height = g_window.getDisplayHeight(), + cpu = g_platform.getCPUName(), + mem = g_platform.getTotalSystemMemory(), + os_name = g_platform.getOSName() + } + local data = json.encode({ + text = text, + version = g_app.getVersion(), + host = g_settings.get('host'), + player = playerData, + details = details + }) + + postId = HTTP.post(Services.feedback, data, function(ret, err) + if err then + tries = tries + 1 + if tries < 3 then + replyEvent = scheduleEvent(send, 1000) + end + end + end) + end + hide() +end \ No newline at end of file diff --git a/800OTClient/modules/client_feedback/feedback.otmod b/800OTClient/modules/client_feedback/feedback.otmod new file mode 100644 index 0000000..e9713cb --- /dev/null +++ b/800OTClient/modules/client_feedback/feedback.otmod @@ -0,0 +1,10 @@ +Module + name: client_feedback + description: Allow to send feedback + author: otclientv8 + website: otclient.ovh + sandboxed: true + dependencies: [ game_interface ] + scripts: [ feedback ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/client_feedback/feedback.otui b/800OTClient/modules/client_feedback/feedback.otui new file mode 100644 index 0000000..023facb --- /dev/null +++ b/800OTClient/modules/client_feedback/feedback.otui @@ -0,0 +1,48 @@ +MainWindow + id: feedbackWindow + size: 400 280 + !text: tr("Feedback/Bug report") + + Label + id: description + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-auto-resize: true + text-align: left + text-wrap: true + !text: tr("Bellow enter your feedback or bug report. Please include as much details as possible. Thank you!") + + MultilineTextEdit + id: text + anchors.top: textScroll.top + anchors.left: parent.left + anchors.right: textScroll.left + anchors.bottom: textScroll.bottom + vertical-scrollbar: textScroll + text-wrap: true + + VerticalScrollBar + id: textScroll + anchors.top: description.bottom + anchors.bottom: okButton.top + anchors.right: parent.right + margin-top: 10 + margin-bottom: 10 + step: 16 + pixels-scroll: true + + Button + id: okButton + !text: tr('Ok') + anchors.bottom: parent.bottom + anchors.right: next.left + margin-right: 10 + width: 60 + + Button + id: cancelButton + !text: tr('Cancel') + anchors.bottom: parent.bottom + anchors.right: parent.right + width: 60 diff --git a/800OTClient/modules/client_locales/locales.lua b/800OTClient/modules/client_locales/locales.lua new file mode 100644 index 0000000..f221f01 --- /dev/null +++ b/800OTClient/modules/client_locales/locales.lua @@ -0,0 +1,177 @@ +dofile 'neededtranslations' + +-- private variables +local defaultLocaleName = 'en' +local installedLocales +local currentLocale +local missingTranslations = {} + +function createWindow() + localesWindow = g_ui.displayUI('locales') + local localesPanel = localesWindow:getChildById('localesPanel') + local layout = localesPanel:getLayout() + local spacing = layout:getCellSpacing() + local size = layout:getCellSize() + + local count = 0 + for name,locale in pairs(installedLocales) do + local widget = g_ui.createWidget('LocalesButton', localesPanel) + widget:setImageSource('/images/flags/' .. name .. '') + widget:setText(locale.languageName) + widget.onClick = function() selectFirstLocale(name) end + count = count + 1 + end + + count = math.max(1, math.min(count, 3)) + localesPanel:setWidth(size.width*count + spacing*(count-1)) + + addEvent(function() addEvent(function() localesWindow:raise() localesWindow:focus() end) end) +end + +function selectFirstLocale(name) + if localesWindow then + localesWindow:destroy() + localesWindow = nil + end + if setLocale(name) then + g_modules.reloadModules() + end + g_settings.save() +end + +-- public functions +function init() + installedLocales = {} + + installLocales('/locales') + + local userLocaleName = g_settings.get('locale', 'false') + if userLocaleName ~= 'false' and setLocale(userLocaleName) then + pdebug('Using configured locale: ' .. userLocaleName) + else + setLocale(defaultLocaleName) + --connect(g_app, { onRun = createWindow }) + end +end + +function terminate() + installedLocales = nil + currentLocale = nil + + --disconnect(g_app, { onRun = createWindow }) +end + +function generateNewTranslationTable(localename) + local locale = installedLocales[localename] + for _i,k in pairs(neededTranslations) do + local trans = locale.translation[k] + k = k:gsub('\n','\\n') + k = k:gsub('\t','\\t') + k = k:gsub('\"','\\\"') + if trans then + trans = trans:gsub('\n','\\n') + trans = trans:gsub('\t','\\t') + trans = trans:gsub('\"','\\\"') + end + if not trans then + print(' ["' .. k .. '"]' .. ' = false,') + else + print(' ["' .. k .. '"]' .. ' = "' .. trans .. '",') + end + end +end + +function installLocale(locale) + if not locale or not locale.name then + error('Unable to install locale.') + end + + if _G.allowedLocales and not _G.allowedLocales[locale.name] then return end + + if locale.name ~= defaultLocaleName then + local updatesNamesMissing = {} + for _,k in pairs(neededTranslations) do + if locale.translation[k] == nil then + updatesNamesMissing[#updatesNamesMissing + 1] = k + end + end + + if #updatesNamesMissing > 0 then + pdebug('Locale \'' .. locale.name .. '\' is missing ' .. #updatesNamesMissing .. ' translations.') + for _,name in pairs(updatesNamesMissing) do + pdebug('["' .. name ..'"] = \"\",') + end + end + end + + local installedLocale = installedLocales[locale.name] + if installedLocale then + for word,translation in pairs(locale.translation) do + installedLocale.translation[word] = translation + end + else + installedLocales[locale.name] = locale + end +end + +function installLocales(directory) + dofiles(directory) +end + +function setLocale(name) + local locale = installedLocales[name] + if locale == currentLocale then return end + if not locale then + pwarning("Locale " .. name .. ' does not exist.') + return false + end + currentLocale = locale + g_settings.set('locale', name) + if onLocaleChanged then onLocaleChanged(name) end + return true +end + +function getInstalledLocales() + return installedLocales +end + +function getCurrentLocale() + return currentLocale +end + +-- global function used to translate texts +function _G.tr(text, ...) + if currentLocale then + if tonumber(text) and currentLocale.formatNumbers then + local number = tostring(text):split('.') + local out = '' + local reverseNumber = number[1]:reverse() + for i=1,#reverseNumber do + out = out .. reverseNumber:sub(i, i) + if i % 3 == 0 and i ~= #number then + out = out .. currentLocale.thousandsSeperator + end + end + + if number[2] then + out = number[2] .. currentLocale.decimalSeperator .. out + end + return out:reverse() + elseif tostring(text) then + local translation = currentLocale.translation[text] + if not translation then + if translation == nil then + if currentLocale.name ~= defaultLocaleName then + if not missingTranslations[text] then + pdebug('Unable to translate: \"' .. text .. '\"') + missingTranslations[text] = true + end + end + end + translation = text + end + return string.format(translation, ...) + end + end + return text +end diff --git a/800OTClient/modules/client_locales/locales.otmod b/800OTClient/modules/client_locales/locales.otmod new file mode 100644 index 0000000..148a1df --- /dev/null +++ b/800OTClient/modules/client_locales/locales.otmod @@ -0,0 +1,9 @@ +Module + name: client_locales + description: Translates texts to selected language + author: baxnie, edubart + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ locales ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/client_locales/locales.otui b/800OTClient/modules/client_locales/locales.otui new file mode 100644 index 0000000..6b5c33e --- /dev/null +++ b/800OTClient/modules/client_locales/locales.otui @@ -0,0 +1,35 @@ +LocalesMainLabel < Label + font: sans-bold-16px + +LocalesButton < UIWidget + size: 96 96 + image-size: 96 96 + image-smooth: true + text-offset: 0 96 + font: verdana-11px-antialised + +UIWindow + id: localesWindow + background-color: #000000 + opacity: 0.90 + clipping: true + anchors.fill: parent + + LocalesMainLabel + !text: tr('Select your language') + text-auto-resize: true + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + margin-top: -100 + + Panel + id: localesPanel + margin-top: 50 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: prev.bottom + anchors.bottom: parent.bottom + layout: + type: grid + cell-size: 96 128 + cell-spacing: 32 + flow: true diff --git a/800OTClient/modules/client_locales/neededtranslations.lua b/800OTClient/modules/client_locales/neededtranslations.lua new file mode 100644 index 0000000..68f8900 --- /dev/null +++ b/800OTClient/modules/client_locales/neededtranslations.lua @@ -0,0 +1,364 @@ +-- generated by ./tools/gen_needed_translations.sh +neededTranslations = { + "1a) Offensive Name", + "1b) Invalid Name Format", + "1c) Unsuitable Name", + "1d) Name Inciting Rule Violation", + "2a) Offensive Statement", + "2b) Spamming", + "2c) Illegal Advertising", + "2d) Off-Topic Public Statement", + "2e) Non-English Public Statement", + "2f) Inciting Rule Violation", + "3a) Bug Abuse", + "3b) Game Weakness Abuse", + "3c) Using Unofficial Software to Play", + "3d) Hacking", + "3e) Multi-Clienting", + "3f) Account Trading or Sharing", + "4a) Threatening Gamemaster", + "4b) Pretending to Have Influence on Rule Enforcement", + "4c) False Report to Gamemaster", + "Accept", + "Account name", + "Account Status:", + "Action:", + "Add", + "Add new VIP", + "Addon 1", + "Addon 2", + "Addon 3", + "Add to VIP list", + "Adjust volume", + "Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nSimply click on Ok to resume your journeys!", + "All", + "All modules and scripts were reloaded.", + "Allow auto chase override", + "Ambient light: %s%%", + "Amount:", + "Amount", + "Anonymous", + "Are you sure you want to logout?", + "Attack", + "Author", + "Autoload", + "Autoload priority", + "Auto login", + "Auto login selected character on next charlist load", + "Axe Fighting", + "Balance:", + "Banishment", + "Banishment + Final Warning", + "Battle", + "Browse", + "Bug report sent.", + "Button Assign", + "Buy", + "Buy Now", + "Buy Offers", + "Buy with backpack", + "Cancel", + "Cannot login while already in game.", + "Cap", + "Capacity", + "Center", + "Channels", + "Character List", + "Classic control", + "Clear current message window", + "Clear Messages", + "Clear object", + "Client needs update.", + "Close", + "Close this channel", + "Club Fighting", + "Combat Controls", + "Comment:", + "Connecting to game server...", + "Connecting to login server...", + "Console", + "Cooldowns", + "Copy message", + "Copy name", + "Copy Name", + "Create Map Mark", + "Create mark", + "Create New Offer", + "Create Offer", + "Current hotkeys:", + "Current hotkey to add: %s", + "Current Offers", + "Default", + "Delete mark", + "Description:", + "Description", + "Destructive Behaviour", + "Detail", + "Details", + "Disable Shared Experience", + "Dismount", + "Display connection speed to the server (milliseconds)", + "Distance Fighting", + "Don\'t stretch/shrink Game Window", + "Edit hotkey text:", + "Edit List", + "Edit Text", + "Enable music", + "Enable Shared Experience", + "Enable smart walking", + "Enable vertical synchronization", + "Enable walk booster", + "Enter Game", + "Enter one name per line.", + "Enter with your account again to update your client.", + "Error", + "Error", + "Excessive Unjustified Player Killing", + "Exclude from private chat", + "Exit", + "Experience", + "Filter list to match your level", + "Filter list to match your vocation", + "Find:", + "Fishing", + "Fist Fighting", + "Follow", + "Force Exit", + "For Your Information", + "Free Account", + "Fullscreen", + "Game", + "Game framerate limit: %s", + "Graphics", + "Graphics card driver not detected", + "Graphics Engine:", + "Head", + "Healing", + "Health Info", + "Health Information", + "Hide monsters", + "Hide non-skull players", + "Hide Npcs", + "Hide Offline", + "Hide party members", + "Hide players", + "Hide spells for higher exp. levels", + "Hide spells for other vocations", + "Hit Points", + "Hold left mouse button to navigate\nScroll mouse middle button to zoom\nRight mouse button to create map marks", + "Hotkeys", + "If you shut down the program, your character might stay in the game.\nClick on 'Logout' to ensure that you character leaves the game properly.\nClick on 'Exit' if you want to exit the program without logging out your character.", + "Ignore", + "Ignore capacity", + "Ignored players:", + "Ignore equipped", + "Ignore List", + "Ignore players", + "Ignore Private Messages", + "Ignore Yelling", + "Interface framerate limit: %s", + "Inventory", + "Invite to Party", + "Invite to private chat", + "IP Address Banishment", + "Item Offers", + "It is empty.", + "Join %s\'s Party", + "Leave Party", + "Level", + "Lifetime Premium Account", + "Limits FPS to 60", + "List of items that you're able to buy", + "List of items that you're able to sell", + "Load", + "Logging out...", + "Login", + "Login Error", + "Login Error", + "Logout", + "Look", + "Magic Level", + "Make sure that your client uses\nthe correct game protocol version", + "Mana", + "Manage hotkeys:", + "Market", + "Market Offers", + "Message of the day", + "Message to ", + "Message to %s", + "Minimap", + "Module Manager", + "Module name", + "Mount", + "Move Stackable Item", + "Move up", + "My Offers", + "Name:", + "Name Report", + "Name Report + Banishment", + "Name Report + Banishment + Final Warning", + "No", + "No graphics card detected, everything will be drawn using the CPU,\nthus the performance will be really bad.\nPlease update your graphics driver to have a better performance.", + "No item selected.", + "No Mount", + "No Outfit", + "No statement has been selected.", + "Notation", + "NPC Trade", + "Offer History", + "Offers", + "Offer Type:", + "Offline Training", + "Ok", + "on %s.\n", + "Open", + "Open a private message channel:", + "Open charlist automatically when starting client", + "Open in new window", + "Open new channel", + "Options", + "Overview", + "Pass Leadership to %s", + "Password", + "Piece Price:", + "Please enter a character name:", + "Please, press the key you wish to add onto your hotkeys manager", + "Please Select", + "Please use this dialog to only report bugs. Do not report rule violations here!", + "Please wait", + "Port", + "Position:", + "Position: %i %i %i", + "Premium Account (%s) days left", + "Price:", + "Primary", + "Protocol", + "Quest Log", + "Randomize", + "Randomize characters outfit", + "Reason:", + "Refresh", + "Refresh Offers", + "Regeneration Time", + "Reject", + "Reload All", + "Remember account and password when starts client", + "Remember password", + "Remove", + "Remove %s", + "Report Bug", + "Reserved for more functionality later.", + "Reset Market", + "Revoke %s\'s Invitation", + "Rotate", + "Rule Violation", + "Save", + "Save Messages", + "Search:", + "Search all items", + "Secondary", + "Select object", + "Select Outfit", + "Select your language", + "Sell", + "Sell Now", + "Sell Offers", + "Send", + "Send automatically", + "Send Message", + "Server", + "Server Log", + "Set Outfit", + "Shielding", + "Show all items", + "Show connection ping", + "Show Depot Only", + "Show event messages in console", + "Show frame rate", + "Show info messages in console", + "Show left panel", + "Show levels in console", + "Show Offline", + "Show private messages in console", + "Show private messages on screen", + "Show Server Messages", + "Show status messages in console", + "Show Text", + "Show timestamps in console", + "Show your depot items only", + "Skills", + "Soul", + "Soul Points", + "Special", + "Speed", + "Spell Cooldowns", + "Spell List", + "Stamina", + "Statement:", + "Statement Report", + "Statistics", + "Stop Attack", + "Stop Follow", + "Support", + "%s: (use object)", + "%s: (use object on target)", + "%s: (use object on yourself)", + "%s: (use object with crosshair)", + "Sword Fighting", + "Terminal", + "There is no way.", + "Title", + "Total Price:", + "Trade", + "Trade with ...", + "Trying to reconnect in %s seconds.", + "Unable to load dat file, please place a valid dat in '%s'", + "Unable to load spr file, please place a valid spr in '%s'", + "Unable to logout.", + "Unignore", + "Unload", + "Update needed", + "Use", + "Use on target", + "Use on yourself", + "Use with ...", + "Version", + "VIP List", + "Voc.", + "Vocation", + "Waiting List", + "Website", + "Weight:", + "Will detect when to use diagonal step based on the\nkeys you are pressing", + "With crosshair", + "Yes", + "You are bleeding", + "You are burning", + "You are cursed", + "You are dazzled", + "You are dead.", + "You are dead", + "You are drowning", + "You are drunk", + "You are electrified", + "You are freezing", + "You are hasted", + "You are hungry", + "You are paralysed", + "You are poisoned", + "You are protected by a magic shield", + "You are strengthened", + "You are within a protection zone", + "You can enter new text.", + "You have %s percent", + "You have %s percent to go", + "You may not logout during a fight", + "You may not logout or enter a protection zone", + "You must enter a comment.", + "You must enter a valid server address and port.", + "You must select a character to login!", + "Your Capacity:", + "You read the following, written by \n%s\n", + "You read the following, written on \n%s.\n", + "Your Money:", +} diff --git a/800OTClient/modules/client_mobile/mobile.lua b/800OTClient/modules/client_mobile/mobile.lua new file mode 100644 index 0000000..b3f47e7 --- /dev/null +++ b/800OTClient/modules/client_mobile/mobile.lua @@ -0,0 +1,216 @@ +local overlay +local keypad +local touchStart = 0 +local updateCursorEvent +local zoomInButton +local zoomOutButton +local keypadButton +local keypadEvent +local keypadMousePos = {x=0.5, y=0.5} +local keypadTicks = 0 + +-- public functions +function init() + if not g_app.isMobile() then return end + overlay = g_ui.displayUI('mobile') + keypad = overlay.keypad + overlay:raise() + + zoomInButton = modules.client_topmenu.addLeftButton('zoomInButton', 'Zoom In', '/images/topbuttons/zoomin', function() g_app.scaleUp() end) + zoomOutButton = modules.client_topmenu.addLeftButton('zoomOutButton', 'Zoom Out', '/images/topbuttons/zoomout', function() g_app.scaleDown() end) + keypadButton = modules.client_topmenu.addLeftGameToggleButton('keypadButton', 'Keypad', '/images/topbuttons/keypad', function() + keypadButton:setChecked(not keypadButton:isChecked()) + if not g_game.isOnline() then + keypad:setVisible(false) + return + end + keypad:setVisible(keypadButton:isChecked()) + end) + keypadButton:setChecked(true) + + scheduleEvent(function() + g_app.scale(5.0) + end, 10) + + connect(overlay, { + onMousePress = onMousePress, + onMouseRelease = onMouseRelease, + onTouchPress = onMousePress, + onTouchRelease = onMouseRelease, + onMouseMove = onMouseMove + }) + connect(keypad, { + onTouchPress = onKeypadTouchPress, + onTouchRelease = onKeypadTouchRelease, + onMouseMove = onKeypadTouchMove + }) + connect(g_game, { + onGameStart = online, + onGameEnd = offline + }) + if g_game.isOnline() then + online() + end +end + +function terminate() + if not g_app.isMobile() then return end + removeEvent(updateCursorEvent) + removeEvent(keypadEvent) + keypadEvent = nil + disconnect(overlay, { + onMousePress = onMousePress, + onMouseRelease = onMouseRelease, + onTouchPress = onMousePress, + onTouchRelease = onMouseRelease, + onMouseMove = onMouseMove + }) + disconnect(keypad, { + onTouchPress = onKeypadTouchPress, + onTouchRelease = onKeypadTouchRelease, + onMouseMove = onKeypadTouchMove + }) + disconnect(g_game, { + onGameStart = online, + onGameEnd = offline + }) + zoomInButton:destroy() + zoomOutButton:destroy() + keypadButton:destroy() + overlay:destroy() + overlay = nil +end + +function hide() + overlay:hide() +end + +function show() + overlay:show() +end + +function online() + if keypadButton:isChecked() then + keypad:raise() + keypad:show() + end +end + +function offline() + keypad:hide() +end + +function onMouseMove(widget, pos, offset) + +end + +function onMousePress(widget, pos, button) + overlay:raise() + if button == MouseTouch then -- touch + overlay:raise() + overlay.cursor:show() + overlay.cursor:setPosition({x=pos.x - 32, y = pos.y - 32}) + touchStart = g_clock.millis() + updateCursor() + else + overlay.cursor:hide() + removeEvent(updateCursorEvent) + end +end + +function onMouseRelease(widget, pos, button) + if button == MouseTouch then + overlay.cursor:hide() + removeEvent(updateCursorEvent) + end +end + +function updateCursor() + removeEvent(updateCursorEvent) + if not g_mouse.isPressed(MouseTouch) then return end + local percent = 100 - math.max(0, math.min(100, (g_clock.millis() - touchStart) / 5)) -- 500 ms + overlay.cursor:setPercent(percent) + if percent > 0 then + overlay.cursor:setOpacity(0.5) + updateCursorEvent = scheduleEvent(updateCursor, 10) + else + overlay.cursor:setOpacity(0.8) + end +end + +function onKeypadTouchMove(widget, pos, offset) + keypadMousePos = {x=(pos.x - widget:getPosition().x) / widget:getWidth(), + y=(pos.y - widget:getPosition().y) / widget:getHeight()} + return true +end + +function onKeypadTouchPress(widget, pos, button) + if button ~= MouseTouch then return false end + keypadTicks = 0 + keypadMousePos = {x=(pos.x - widget:getPosition().x) / widget:getWidth(), + y=(pos.y - widget:getPosition().y) / widget:getHeight()} + executeWalk() + return true +end + +function onKeypadTouchRelease(widget, pos, button) + if button ~= MouseTouch then return false end + keypadMousePos = {x=(pos.x - widget:getPosition().x) / widget:getWidth(), + y=(pos.y - widget:getPosition().y) / widget:getHeight()} + executeWalk() + removeEvent(keypadEvent) + keypad.pointer:setMarginTop(0) + keypad.pointer:setMarginLeft(0) + return true +end + +function executeWalk() + removeEvent(keypadEvent) + keypadEvent = nil + if not modules.game_walking or not g_mouse.isPressed(MouseTouch) then + keypad.pointer:setMarginTop(0) + keypad.pointer:setMarginLeft(0) + return + end + keypadEvent = scheduleEvent(executeWalk, 20) + keypadMousePos.x = math.min(1, math.max(0, keypadMousePos.x)) + keypadMousePos.y = math.min(1, math.max(0, keypadMousePos.y)) + local angle = math.atan2(keypadMousePos.x - 0.5, keypadMousePos.y - 0.5) + local maxTop = math.abs(math.cos(angle)) * 75 + local marginTop = math.max(-maxTop, math.min(maxTop, (keypadMousePos.y - 0.5) * 150)) + local maxLeft = math.abs(math.sin(angle)) * 75 + local marginLeft = math.max(-maxLeft, math.min(maxLeft, (keypadMousePos.x - 0.5) * 150)) + keypad.pointer:setMarginTop(marginTop) + keypad.pointer:setMarginLeft(marginLeft) + local dir + if keypadMousePos.y < 0.3 and keypadMousePos.x < 0.3 then + dir = Directions.NorthWest + elseif keypadMousePos.y < 0.3 and keypadMousePos.x > 0.7 then + dir = Directions.NorthEast + elseif keypadMousePos.y > 0.7 and keypadMousePos.x < 0.3 then + dir = Directions.SouthWest + elseif keypadMousePos.y > 0.7 and keypadMousePos.x > 0.7 then + dir = Directions.SouthEast + end + if not dir and (math.abs(keypadMousePos.y - 0.5) > 0.1 or math.abs(keypadMousePos.x - 0.5) > 0.1) then + if math.abs(keypadMousePos.y - 0.5) > math.abs(keypadMousePos.x - 0.5) then + if keypadMousePos.y < 0.5 then + dir = Directions.North + else + dir = Directions.South + end + else + if keypadMousePos.x < 0.5 then + dir = Directions.West + else + dir = Directions.East + end + end + end + if dir then + modules.game_walking.walk(dir, keypadTicks) + if keypadTicks == 0 then + keypadTicks = 100 + end + end +end \ No newline at end of file diff --git a/800OTClient/modules/client_mobile/mobile.otmod b/800OTClient/modules/client_mobile/mobile.otmod new file mode 100644 index 0000000..da7a329 --- /dev/null +++ b/800OTClient/modules/client_mobile/mobile.otmod @@ -0,0 +1,9 @@ +Module + name: client_mobile + description: Handles the mobile interface for smartphones + author: otclient@otclient.ovh + website: http://otclient.net + sandboxed: true + scripts: [ mobile ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/client_mobile/mobile.otui b/800OTClient/modules/client_mobile/mobile.otui new file mode 100644 index 0000000..c85e83c --- /dev/null +++ b/800OTClient/modules/client_mobile/mobile.otui @@ -0,0 +1,39 @@ +UIWidget + anchors.fill: parent + focusable: false + phantom: true + + UIProgressRect + id: cursor + size: 64 64 + background: #FF5858 + percent: 100 + visible: false + x: 0 + y: 0 + focusable: false + phantom: true + + UIWidget + id: keypad + size: 200 150 + anchors.bottom: parent.bottom + anchors.right: parent.right + phantom: false + focusable: false + visible: false + background: #00000044 + image-source: /images/game/mobile/keypad + image-fixed-ratio: true + image-rect: 25 0 150 150 + + UIWidget + id: pointer + size: 49 49 + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + image-source: /images/game/mobile/keypad_pointer + image-fixed-ratio: true + phantom: true + focusable: false + \ No newline at end of file diff --git a/800OTClient/modules/client_options/audio.otui b/800OTClient/modules/client_options/audio.otui new file mode 100644 index 0000000..4e803b4 --- /dev/null +++ b/800OTClient/modules/client_options/audio.otui @@ -0,0 +1,36 @@ +OptionPanel + OptionCheckBox + id: enableAudio + !text: tr('Enable audio') + + OptionCheckBox + id: enableMusicSound + !text: tr('Enable music sound') + + Label + id: musicSoundVolumeLabel + !text: tr('Music volume: %d', 100) + margin-top: 6 + @onSetup: | + local value = modules.client_options.getOption('musicSoundVolume') + self:setText(tr('Music volume: %d', value)) + + OptionScrollbar + id: musicSoundVolume + margin-top: 3 + minimum: 0 + maximum: 100 + + Label + id: botSoundVolumeLabel + !text: tr('Bot sound volume: %d', 100) + margin-top: 6 + @onSetup: | + local value = modules.client_options.getOption('botSoundVolume') + self:setText(tr('Bot sound volume: %d', value)) + + OptionScrollbar + id: botSoundVolume + margin-top: 3 + minimum: 0 + maximum: 100 diff --git a/800OTClient/modules/client_options/console.otui b/800OTClient/modules/client_options/console.otui new file mode 100644 index 0000000..a0b0bae --- /dev/null +++ b/800OTClient/modules/client_options/console.otui @@ -0,0 +1,28 @@ +OptionPanel + OptionCheckBox + id: showInfoMessagesInConsole + !text: tr('Show info messages in console') + + OptionCheckBox + id: showEventMessagesInConsole + !text: tr('Show event messages in console') + + OptionCheckBox + id: showStatusMessagesInConsole + !text: tr('Show status messages in console') + + OptionCheckBox + id: showTimestampsInConsole + !text: tr('Show timestamps in console') + + OptionCheckBox + id: showLevelsInConsole + !text: tr('Show levels in console') + + OptionCheckBox + id: showPrivateMessagesInConsole + !text: tr('Show private messages in console') + + OptionCheckBox + id: showPrivateMessagesOnScreen + !text: tr('Show private messages on screen') \ No newline at end of file diff --git a/800OTClient/modules/client_options/custom.otui b/800OTClient/modules/client_options/custom.otui new file mode 100644 index 0000000..a047753 --- /dev/null +++ b/800OTClient/modules/client_options/custom.otui @@ -0,0 +1,125 @@ +OptionPanel + Label + text: Client user features profile + + ComboBox + id: profile + margin-top: 3 + @onOptionChange: modules.client_options.setOption(self:getId(), self.currentIndex) + @onSetup: | + self:addOption("1") + self:addOption("2") + self:addOption("3") + self:addOption("4") + self:addOption("5") + self:addOption("6") + self:addOption("7") + self:addOption("8") + self:addOption("9") + self:addOption("10") + + Label + + OptionCheckBox + id: topBar + !text: tr('Show customizable top status bar') + + OptionCheckBox + id: topHealtManaBar + !text: tr('Show player top health and mana bar') + + OptionCheckBox + id: showHealthManaCircle + !text: tr('Show health and mana circle') + $mobile: + visible: false + + Label + margin-top: 5 + text: Show Bottom Action Bars: + + Panel + margin-top: 2 + height: 16 + layout: + type: horizontalBox + + OptionCheckBox + id: actionbarBottom1 + !text: tr('Bar 1') + width: 60 + + OptionCheckBox + id: actionbarBottom2 + !text: tr('Bar 2') + width: 60 + + OptionCheckBox + id: actionbarBottom3 + !text: tr('Bar 3') + width: 60 + + Label + text: Show Left Action Bars: + $mobile: + visible: false + + Panel + margin-top: 2 + height: 16 + + $mobile: + visible: false + + layout: + type: horizontalBox + + OptionCheckBox + id: actionbarLeft1 + !text: tr('Bar 1') + width: 60 + + OptionCheckBox + id: actionbarLeft2 + !text: tr('Bar 2') + width: 60 + + OptionCheckBox + id: actionbarLeft3 + !text: tr('Bar 3') + width: 60 + + Label + text: Show Right Action Bars: + $mobile: + visible: false + + Panel + margin-top: 2 + height: 16 + layout: + type: horizontalBox + + $mobile: + visible: false + + OptionCheckBox + id: actionbarRight1 + !text: tr('Bar 1') + width: 60 + + OptionCheckBox + id: actionbarRight2 + !text: tr('Bar 2') + width: 60 + + OptionCheckBox + id: actionbarRight3 + !text: tr('Bar 3') + width: 60 + + Label + + OptionCheckBox + id: actionbarLock + !text: tr('Disable action bar hotkeys when chat mode is on') \ No newline at end of file diff --git a/800OTClient/modules/client_options/game.otui b/800OTClient/modules/client_options/game.otui new file mode 100644 index 0000000..b0adb0b --- /dev/null +++ b/800OTClient/modules/client_options/game.otui @@ -0,0 +1,147 @@ +OptionPanel + OptionCheckBox + id: classicControl + !text: tr('Classic control') + + $mobile: + visible: false + + OptionCheckBox + id: autoChaseOverride + !text: tr('Allow auto chase override') + + OptionCheckBox + id: displayText + !text: tr('Display text messages') + + OptionCheckBox + id: wsadWalking + !text: tr('Enable WSAD walking') + !tooltip: tr('Disable chat and allow walk using WSAD keys') + $mobile: + visible: false + + OptionCheckBox + id: dash + !text: tr('Enable fast walking (DASH)') + !tooltip: tr('Allows to execute next move without server confirmation of previous one') + + OptionCheckBox + id: smartWalk + !text: tr('Enable smart walking') + !tooltip: tr('Will detect when to use diagonal step based on the\nkeys you are pressing') + + Label + id: hotkeyDelayLabel + margin-top: 10 + !tooltip: tr('Give you some time to make a turn while walking if you press many keys simultaneously') + @onSetup: | + local value = modules.client_options.getOption('hotkeyDelay') + self:setText(tr('Hotkey delay: %s ms', value)) + + OptionScrollbar + id: hotkeyDelay + margin-top: 3 + minimum: 5 + maximum: 50 + + Label + id: walkFirstStepDelayLabel + margin-top: 10 + @onSetup: | + local value = modules.client_options.getOption('walkFirstStepDelay') + self:setText(tr('Walk delay after first step: %s ms', value)) + + $mobile: + visible: false + + OptionScrollbar + id: walkFirstStepDelay + margin-top: 3 + minimum: 50 + maximum: 300 + + $mobile: + visible: false + + Label + id: walkTurnDelayLabel + margin-top: 10 + @onSetup: | + local value = modules.client_options.getOption('walkTurnDelay') + self:setText(tr('Walk delay after turn: %s ms', value)) + + $mobile: + visible: false + + OptionScrollbar + id: walkTurnDelay + margin-top: 3 + minimum: 0 + maximum: 300 + + $mobile: + visible: false + + Label + id: walkCtrlTurnDelayLabel + margin-top: 10 + $mobile: + visible: false + @onSetup: | + local value = modules.client_options.getOption('walkTurnDelay') + self:setText(tr('Walk delay after ctrl turn: %s ms', value)) + + OptionScrollbar + id: walkCtrlTurnDelay + margin-top: 3 + minimum: 0 + maximum: 300 + $mobile: + visible: false + + Label + id: walkStairsDelayLabel + margin-top: 10 + @onSetup: | + local value = modules.client_options.getOption('walkStairsDelay') + self:setText(tr('Walk delay after floor change: %s ms', value)) + $mobile: + visible: false + + OptionScrollbar + id: walkStairsDelay + margin-top: 3 + minimum: 0 + maximum: 300 + $mobile: + visible: false + + Label + id: walkTeleportDelayLabel + margin-top: 10 + @onSetup: | + local value = modules.client_options.getOption('walkTeleportDelay') + self:setText(tr('Walk delay after teleport: %s ms', value)) + $mobile: + visible: false + + OptionScrollbar + id: walkTeleportDelay + margin-top: 3 + minimum: 0 + maximum: 300 + $mobile: + visible: false + + Panel + height: 30 + margin-top: 10 + + Button + id: changeLocale + !text: tr('Change language') + @onClick: modules.client_locales.createWindow() + anchors.left: parent.left + anchors.top: parent.top + width: 150 diff --git a/800OTClient/modules/client_options/graphics.otui b/800OTClient/modules/client_options/graphics.otui new file mode 100644 index 0000000..dd75962 --- /dev/null +++ b/800OTClient/modules/client_options/graphics.otui @@ -0,0 +1,100 @@ +OptionPanel + Label + text-wrap: false + @onSetup: | + self:setText(tr("GPU: ") .. g_graphics.getRenderer()) + + Label + text-wrap: false + @onSetup: | + self:setText(tr("Version: ") .. g_graphics.getVersion()) + + HorizontalSeparator + id: separator + margin: 5 5 5 5 + + OptionCheckBox + id: vsync + !text: tr('Enable vertical synchronization') + !tooltip: tr('Limits FPS (usually to 60)') + + OptionCheckBox + id: showFps + !text: tr('Show frame rate') + + OptionCheckBox + id: enableLights + !text: tr('Enable lights') + + OptionCheckBox + id: fullscreen + !text: tr('Fullscreen') + tooltip: Ctrl+Shift+F + + OptionCheckBox + id: antialiasing + !text: tr('Antialiasing') + + Label + margin-top: 12 + id: optimizationLevelLabel + !text: tr("Optimization level") + + ComboBox + id: optimizationLevel + margin-top: 3 + margin-right: 2 + margin-left: 2 + @onOptionChange: modules.client_options.setOption(self:getId(), self.currentIndex) + @onSetup: | + self:addOption("Automatic") + self:addOption("None") + self:addOption("Low") + self:addOption("Medium") + self:addOption("High") + self:addOption("Maximum") + + Label + !text: tr('High/Maximum optimization level may cause visual defects.') + margin-top: 5 + + Label + id: backgroundFrameRateLabel + !text: tr('Game framerate limit: %s', 'max') + margin-top: 12 + @onSetup: | + local value = modules.client_options.getOption('backgroundFrameRate') + local text = value + if value <= 0 or value >= 201 then + text = 'max' + end + self:setText(tr('Game framerate limit: %s', text)) + + OptionScrollbar + id: backgroundFrameRate + margin-top: 3 + minimum: 10 + maximum: 201 + + Label + id: ambientLightLabel + margin-top: 6 + @onSetup: | + local value = modules.client_options.getOption('ambientLight') + self:setText(tr('Ambient light: %s%%', value)) + + OptionScrollbar + id: ambientLight + margin-top: 3 + minimum: 0 + maximum: 100 + + Label + id: tips + margin-top: 20 + text-auto-resize: true + text-align: left + text-wrap: true + !text: tr("If you have FPS issues:\n- Use OpenGL version (_gl)\n- Disable vertical synchronization\n- Set higher optimization level\n- Lower screen resolution\nOr report it on forum: http://otclient.net") + $mobile: + visible: false \ No newline at end of file diff --git a/800OTClient/modules/client_options/interface.otui b/800OTClient/modules/client_options/interface.otui new file mode 100644 index 0000000..be5630e --- /dev/null +++ b/800OTClient/modules/client_options/interface.otui @@ -0,0 +1,177 @@ +OptionPanel + Label + width: 130 + id: layoutLabel + !text: tr("Layout (change requries client restart)") + $mobile: + visible: false + + ComboBox + id: layout + margin-top: 3 + margin-right: 2 + margin-left: 2 + $mobile: + visible: false + @onOptionChange: modules.client_options.setOption(self:getId(), self:getCurrentOption().text) + @onSetup: | + self:addOption("Default") + for _, file in ipairs(g_resources.listDirectoryFiles("/layouts", false, true)) do + if g_resources.directoryExists("/layouts/" .. file) then + self:addOption(file:gsub("^%l", string.upper)) + end + end + + OptionCheckBox + id: classicView + !text: tr('Classic view') + margin-top: 5 + + $mobile: + visible: false + + OptionCheckBox + id: cacheMap + !text: tr('Cache map (for non-classic view)') + + $mobile: + visible: false + + OptionCheckBox + id: showPing + !text: tr('Show connection ping') + !tooltip: tr('Display connection speed to the server (milliseconds)') + + OptionCheckBox + id: displayNames + !text: tr('Display creature names') + + OptionCheckBox + id: displayHealth + !text: tr('Display creature health bars') + + OptionCheckBox + id: displayHealthOnTop + !text: tr('Display creature health bars above texts') + $mobile: + visible: false + + OptionCheckBox + id: hidePlayerBars + !text: tr('Show player health bar') + + OptionCheckBox + id: displayMana + !text: tr('Show player mana bar') + $mobile: + visible: false + + OptionCheckBox + id: highlightThingsUnderCursor + !text: tr('Highlight things under cursor') + + Panel + height: 40 + margin-top: 3 + + Label + width: 90 + anchors.left: parent.left + anchors.top: parent.top + id: leftPanelsLabel + !text: tr("Left panels") + + Label + width: 90 + anchors.left: prev.right + anchors.top: prev.top + id: rightPanelsLabel + !text: tr("Right panels") + + Label + width: 130 + anchors.left: prev.right + anchors.top: prev.top + id: backpackPanelLabel + !text: tr("Container's panel") + !tooltip: tr("Open new containers in selected panel") + + ComboBox + id: leftPanels + anchors.left: leftPanelsLabel.left + anchors.right: leftPanelsLabel.right + anchors.top: leftPanelsLabel.bottom + margin-top: 3 + margin-right: 20 + @onOptionChange: modules.client_options.setOption(self:getId(), self.currentIndex) + @onSetup: | + self:addOption("0") + self:addOption("1") + self:addOption("2") + self:addOption("3") + self:addOption("4") + + ComboBox + id: rightPanels + anchors.left: rightPanelsLabel.left + anchors.right: rightPanelsLabel.right + anchors.top: rightPanelsLabel.bottom + margin-top: 3 + margin-right: 20 + @onOptionChange: modules.client_options.setOption(self:getId(), self.currentIndex) + @onSetup: | + self:addOption("1") + self:addOption("2") + self:addOption("3") + self:addOption("4") + + ComboBox + id: containerPanel + anchors.left: backpackPanelLabel.left + anchors.right: backpackPanelLabel.right + anchors.top: backpackPanelLabel.bottom + margin-top: 3 + @onOptionChange: modules.client_options.setOption(self:getId(), self.currentIndex) + @onSetup: | + self:addOption("1st left panel") + self:addOption("2nd left panel") + self:addOption("3rd left panel") + self:addOption("4th left panel") + self:addOption("1st right panel") + self:addOption("2nd right panel") + self:addOption("3rd right panel") + self:addOption("4th right panel") + + Label + margin-top: 3 + id: crosshairLabel + !text: tr("Crosshair") + + ComboBox + id: crosshair + margin-top: 3 + margin-right: 2 + margin-left: 2 + @onOptionChange: modules.client_options.setOption(self:getId(), self.currentIndex) + @onSetup: | + self:addOption("None") + self:addOption("Default") + self:addOption("Full") + + Label + id: floorFadingLabel + margin-top: 6 + @onSetup: | + local value = modules.client_options.getOption('floorFading') + self:setText(tr('Floor fading: %s ms', value)) + + OptionScrollbar + id: floorFading + margin-top: 3 + minimum: 0 + maximum: 2000 + + Label + id: floorFadingLabel2 + margin-top: 6 + !text: (tr('Floor fading doesn\'t work with enabled light')) diff --git a/800OTClient/modules/client_options/options.lua b/800OTClient/modules/client_options/options.lua new file mode 100644 index 0000000..8c68c23 --- /dev/null +++ b/800OTClient/modules/client_options/options.lua @@ -0,0 +1,432 @@ +local defaultOptions = { + layout = DEFAULT_LAYOUT, -- set in init.lua + vsync = true, + showFps = true, + showPing = true, + fullscreen = false, + classicView = not g_app.isMobile(), + cacheMap = g_app.isMobile(), + classicControl = not g_app.isMobile(), + smartWalk = false, + dash = false, + autoChaseOverride = true, + showStatusMessagesInConsole = true, + showEventMessagesInConsole = true, + showInfoMessagesInConsole = true, + showTimestampsInConsole = true, + showLevelsInConsole = true, + showPrivateMessagesInConsole = true, + showPrivateMessagesOnScreen = true, + rightPanels = 1, + leftPanels = g_app.isMobile() and 1 or 2, + containerPanel = 8, + backgroundFrameRate = 60, + enableAudio = true, + enableMusicSound = false, + musicSoundVolume = 100, + botSoundVolume = 100, + enableLights = false, + floorFading = 500, + crosshair = 2, + ambientLight = 100, + optimizationLevel = 1, + displayNames = true, + displayHealth = true, + displayMana = true, + displayHealthOnTop = false, + showHealthManaCircle = false, + hidePlayerBars = false, + highlightThingsUnderCursor = true, + topHealtManaBar = true, + displayText = true, + dontStretchShrink = false, + turnDelay = 30, + hotkeyDelay = 30, + + wsadWalking = false, + walkFirstStepDelay = 200, + walkTurnDelay = 100, + walkStairsDelay = 50, + walkTeleportDelay = 200, + walkCtrlTurnDelay = 150, + + topBar = true, + + actionbarBottom1 = true, + actionbarBottom2 = false, + actionbarBottom3 = false, + + actionbarLeft1 = false, + actionbarLeft2 = false, + actionbarLeft3 = false, + + actionbarRight1 = false, + actionbarRight2 = false, + actionbarRight3 = false, + + actionbarLock = false, + + profile = 1, + + antialiasing = true +} + +local optionsWindow +local optionsButton +local optionsTabBar +local options = {} +local extraOptions = {} +local generalPanel +local interfacePanel +local consolePanel +local graphicsPanel +local audioPanel +local customPanel +local extrasPanel +local audioButton + +function init() + for k,v in pairs(defaultOptions) do + g_settings.setDefault(k, v) + options[k] = v + end + for _, v in ipairs(g_extras.getAll()) do + extraOptions[v] = g_extras.get(v) + g_settings.setDefault("extras_" .. v, extraOptions[v]) + end + + optionsWindow = g_ui.displayUI('options') + optionsWindow:hide() + + optionsTabBar = optionsWindow:getChildById('optionsTabBar') + optionsTabBar:setContentWidget(optionsWindow:getChildById('optionsTabContent')) + + g_keyboard.bindKeyDown('Ctrl+Shift+F', function() toggleOption('fullscreen') end) + g_keyboard.bindKeyDown('Ctrl+N', toggleDisplays) + + generalPanel = g_ui.loadUI('game') + optionsTabBar:addTab(tr('Game'), generalPanel, '/images/optionstab/game') + + interfacePanel = g_ui.loadUI('interface') + optionsTabBar:addTab(tr('Interface'), interfacePanel, '/images/optionstab/game') + + consolePanel = g_ui.loadUI('console') + optionsTabBar:addTab(tr('Console'), consolePanel, '/images/optionstab/console') + + graphicsPanel = g_ui.loadUI('graphics') + optionsTabBar:addTab(tr('Graphics'), graphicsPanel, '/images/optionstab/graphics') + + audioPanel = g_ui.loadUI('audio') + optionsTabBar:addTab(tr('Audio'), audioPanel, '/images/optionstab/audio') + + + extrasPanel = g_ui.createWidget('OptionPanel') + for _, v in ipairs(g_extras.getAll()) do + local extrasButton = g_ui.createWidget('OptionCheckBox') + extrasButton:setId(v) + extrasButton:setText(g_extras.getDescription(v)) + extrasPanel:addChild(extrasButton) + end + if not g_game.getFeature(GameNoDebug) and not g_app.isMobile() then + optionsTabBar:addTab(tr('Extras'), extrasPanel, '/images/optionstab/extras') + end + + customPanel = g_ui.loadUI('custom') + optionsTabBar:addTab(tr('Custom'), customPanel, '/images/optionstab/features') + + optionsButton = modules.client_topmenu.addLeftButton('optionsButton', tr('Options'), '/images/topbuttons/options', toggle) + audioButton = modules.client_topmenu.addLeftButton('audioButton', tr('Audio'), '/images/topbuttons/audio', function() toggleOption('enableAudio') end) + if g_app.isMobile() then + audioButton:hide() + end + + addEvent(function() setup() end) + + connect(g_game, { onGameStart = online, + onGameEnd = offline }) +end + +function terminate() + disconnect(g_game, { onGameStart = online, + onGameEnd = offline }) + + g_keyboard.unbindKeyDown('Ctrl+Shift+F') + g_keyboard.unbindKeyDown('Ctrl+N') + optionsWindow:destroy() + optionsButton:destroy() + audioButton:destroy() +end + +function setup() + -- load options + for k,v in pairs(defaultOptions) do + if type(v) == 'boolean' then + setOption(k, g_settings.getBoolean(k), true) + elseif type(v) == 'number' then + setOption(k, g_settings.getNumber(k), true) + elseif type(v) == 'string' then + setOption(k, g_settings.getString(k), true) + end + end + + for _, v in ipairs(g_extras.getAll()) do + g_extras.set(v, g_settings.getBoolean("extras_" .. v)) + local widget = extrasPanel:recursiveGetChildById(v) + if widget then + widget:setChecked(g_extras.get(v)) + end + end + + if g_game.isOnline() then + online() + end +end + +function toggle() + if optionsWindow:isVisible() then + hide() + else + show() + end +end + +function show() + optionsWindow:show() + optionsWindow:raise() + optionsWindow:focus() +end + +function hide() + optionsWindow:hide() +end + +function toggleDisplays() + if options['displayNames'] and options['displayHealth'] and options['displayMana'] then + setOption('displayNames', false) + elseif options['displayHealth'] then + setOption('displayHealth', false) + setOption('displayMana', false) + else + if not options['displayNames'] and not options['displayHealth'] then + setOption('displayNames', true) + else + setOption('displayHealth', true) + setOption('displayMana', true) + end + end +end + +function toggleOption(key) + setOption(key, not getOption(key)) +end + +function setOption(key, value, force) + if extraOptions[key] ~= nil then + g_extras.set(key, value) + g_settings.set("extras_" .. key, value) + if key == "debugProxy" and modules.game_proxy then + if value then + modules.game_proxy.show() + else + modules.game_proxy.hide() + end + end + return + end + + if modules.game_interface == nil then + return + end + + if not force and options[key] == value then return end + local gameMapPanel = modules.game_interface.getMapPanel() + + if key == 'vsync' then + g_window.setVerticalSync(value) + elseif key == 'showFps' then + modules.client_topmenu.setFpsVisible(value) + if modules.game_stats and modules.game_stats.ui.fps then + modules.game_stats.ui.fps:setVisible(value) + end + elseif key == 'showPing' then + modules.client_topmenu.setPingVisible(value) + if modules.game_stats and modules.game_stats.ui.ping then + modules.game_stats.ui.ping:setVisible(value) + end + elseif key == 'fullscreen' then + g_window.setFullscreen(value) + elseif key == 'enableAudio' then + if g_sounds ~= nil then + g_sounds.setAudioEnabled(value) + end + if value then + audioButton:setIcon('/images/topbuttons/audio') + else + audioButton:setIcon('/images/topbuttons/audio_mute') + end + elseif key == 'enableMusicSound' then + if g_sounds ~= nil then + g_sounds.getChannel(SoundChannels.Music):setEnabled(value) + end + elseif key == 'musicSoundVolume' then + if g_sounds ~= nil then + g_sounds.getChannel(SoundChannels.Music):setGain(value/100) + end + audioPanel:getChildById('musicSoundVolumeLabel'):setText(tr('Music volume: %d', value)) + elseif key == 'botSoundVolume' then + if g_sounds ~= nil then + g_sounds.getChannel(SoundChannels.Bot):setGain(value/100) + end + audioPanel:getChildById('botSoundVolumeLabel'):setText(tr('Bot sound volume: %d', value)) + elseif key == 'showHealthManaCircle' then + modules.game_healthinfo.healthCircle:setVisible(value) + modules.game_healthinfo.healthCircleFront:setVisible(value) + modules.game_healthinfo.manaCircle:setVisible(value) + modules.game_healthinfo.manaCircleFront:setVisible(value) + elseif key == 'backgroundFrameRate' then + local text, v = value, value + if value <= 0 or value >= 201 then text = 'max' v = 0 end + graphicsPanel:getChildById('backgroundFrameRateLabel'):setText(tr('Game framerate limit: %s', text)) + g_app.setMaxFps(v) + elseif key == 'enableLights' then + gameMapPanel:setDrawLights(value and options['ambientLight'] < 100) + graphicsPanel:getChildById('ambientLight'):setEnabled(value) + graphicsPanel:getChildById('ambientLightLabel'):setEnabled(value) + elseif key == 'floorFading' then + gameMapPanel:setFloorFading(value) + interfacePanel:getChildById('floorFadingLabel'):setText(tr('Floor fading: %s ms', value)) + elseif key == 'crosshair' then + if value == 1 then + gameMapPanel:setCrosshair("") + elseif value == 2 then + gameMapPanel:setCrosshair("/images/crosshair/default.png") + elseif value == 3 then + gameMapPanel:setCrosshair("/images/crosshair/full.png") + end + elseif key == 'ambientLight' then + graphicsPanel:getChildById('ambientLightLabel'):setText(tr('Ambient light: %s%%', value)) + gameMapPanel:setMinimumAmbientLight(value/100) + gameMapPanel:setDrawLights(options['enableLights'] and value < 100) + elseif key == 'optimizationLevel' then + g_adaptiveRenderer.setLevel(value - 2) + elseif key == 'displayNames' then + gameMapPanel:setDrawNames(value) + elseif key == 'displayHealth' then + gameMapPanel:setDrawHealthBars(value) + elseif key == 'displayMana' then + gameMapPanel:setDrawManaBar(value) + elseif key == 'displayHealthOnTop' then + gameMapPanel:setDrawHealthBarsOnTop(value) + elseif key == 'hidePlayerBars' then + gameMapPanel:setDrawPlayerBars(value) + elseif key == 'topHealtManaBar' then + modules.game_healthinfo.topHealthBar:setVisible(value) + modules.game_healthinfo.topManaBar:setVisible(value) + elseif key == 'displayText' then + gameMapPanel:setDrawTexts(value) + elseif key == 'dontStretchShrink' then + addEvent(function() + modules.game_interface.updateStretchShrink() + end) + elseif key == 'dash' then + if value then + g_game.setMaxPreWalkingSteps(2) + else + g_game.setMaxPreWalkingSteps(1) + end + elseif key == 'wsadWalking' then + if modules.game_console and modules.game_console.consoleToggleChat:isChecked() ~= value then + modules.game_console.consoleToggleChat:setChecked(value) + end + elseif key == 'hotkeyDelay' then + generalPanel:getChildById('hotkeyDelayLabel'):setText(tr('Hotkey delay: %s ms', value)) + elseif key == 'walkFirstStepDelay' then + generalPanel:getChildById('walkFirstStepDelayLabel'):setText(tr('Walk delay after first step: %s ms', value)) + elseif key == 'walkTurnDelay' then + generalPanel:getChildById('walkTurnDelayLabel'):setText(tr('Walk delay after turn: %s ms', value)) + elseif key == 'walkStairsDelay' then + generalPanel:getChildById('walkStairsDelayLabel'):setText(tr('Walk delay after floor change: %s ms', value)) + elseif key == 'walkTeleportDelay' then + generalPanel:getChildById('walkTeleportDelayLabel'):setText(tr('Walk delay after teleport: %s ms', value)) + elseif key == 'walkCtrlTurnDelay' then + generalPanel:getChildById('walkCtrlTurnDelayLabel'):setText(tr('Walk delay after ctrl turn: %s ms', value)) + elseif key == "antialiasing" then + g_app.setSmooth(value) + end + + -- change value for keybind updates + for _,panel in pairs(optionsTabBar:getTabsPanel()) do + local widget = panel:recursiveGetChildById(key) + if widget then + if widget:getStyle().__class == 'UICheckBox' then + widget:setChecked(value) + elseif widget:getStyle().__class == 'UIScrollBar' then + widget:setValue(value) + elseif widget:getStyle().__class == 'UIComboBox' then + if type(value) == "string" then + widget:setCurrentOption(value, true) + break + end + if value == nil or value < 1 then + value = 1 + end + if widget.currentIndex ~= value then + widget:setCurrentIndex(value, true) + end + end + break + end + end + + g_settings.set(key, value) + options[key] = value + + if key == "profile" then + modules.client_profiles.onProfileChange() + end + + if key == 'classicView' or key == 'rightPanels' or key == 'leftPanels' or key == 'cacheMap' then + modules.game_interface.refreshViewMode() + elseif key:find("actionbar") then + modules.game_actionbar.show() + end + + if key == 'topBar' then + modules.game_topbar.show() + end +end + +function getOption(key) + return options[key] +end + +function addTab(name, panel, icon) + optionsTabBar:addTab(name, panel, icon) +end + +function addButton(name, func, icon) + optionsTabBar:addButton(name, func, icon) +end + +-- hide/show + +function online() + setLightOptionsVisibility(not g_game.getFeature(GameForceLight)) + g_app.setSmooth(g_settings.getBoolean("antialiasing")) +end + +function offline() + setLightOptionsVisibility(true) +end + +-- classic view + +-- graphics +function setLightOptionsVisibility(value) + graphicsPanel:getChildById('enableLights'):setEnabled(value) + graphicsPanel:getChildById('ambientLightLabel'):setEnabled(value) + graphicsPanel:getChildById('ambientLight'):setEnabled(value) + interfacePanel:getChildById('floorFading'):setEnabled(value) + interfacePanel:getChildById('floorFadingLabel'):setEnabled(value) + interfacePanel:getChildById('floorFadingLabel2'):setEnabled(value) +end diff --git a/800OTClient/modules/client_options/options.otmod b/800OTClient/modules/client_options/options.otmod new file mode 100644 index 0000000..3fc266b --- /dev/null +++ b/800OTClient/modules/client_options/options.otmod @@ -0,0 +1,9 @@ +Module + name: client_options + description: Create the options window + author: edubart, BeniS + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ options ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/client_options/options.otui b/800OTClient/modules/client_options/options.otui new file mode 100644 index 0000000..061c46b --- /dev/null +++ b/800OTClient/modules/client_options/options.otui @@ -0,0 +1,48 @@ +OptionCheckBox < CheckBox + @onCheckChange: modules.client_options.setOption(self:getId(), self:isChecked()) + height: 16 + + $!first: + margin-top: 2 + +OptionScrollbar < HorizontalScrollBar + step: 1 + @onValueChange: modules.client_options.setOption(self:getId(), self:getValue()) + +OptionPanel < Panel + layout: + type: verticalBox + +MainWindow + id: optionsWindow + !text: tr('Options') + size: 490 500 + $mobile: + size: 490 360 + + @onEnter: modules.client_options.hide() + @onEscape: modules.client_options.hide() + + TabBarVertical + id: optionsTabBar + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + + Panel + id: optionsTabContent + anchors.top: optionsTabBar.top + anchors.left: optionsTabBar.right + anchors.right: parent.right + anchors.bottom: optionsTabBar.bottom + margin-left: 10 + margin-top: 3 + + Button + !text: tr('Ok') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: | + g_settings.save() + modules.client_options.hide() diff --git a/800OTClient/modules/client_profiles/profiles.lua b/800OTClient/modules/client_profiles/profiles.lua new file mode 100644 index 0000000..eb38948 --- /dev/null +++ b/800OTClient/modules/client_profiles/profiles.lua @@ -0,0 +1,160 @@ +local settings = {} +ChangedProfile = false + +function init() + connect(g_game, { + onGameStart = online, + onGameEnd = offline + }) + +end + +function terminate() + disconnect(g_game, { + onGameStart = online, + onGameEnd = offline + }) +end + +-- loads settings on character login +function online() + ChangedProfile = false + + -- startup arguments has higher priority than settings + local index = getProfileFromStartupArgument() + if index then + setProfileOption(index) + end + + load() + + if not index then + setProfileOption(getProfileFromSettings() or 1) + end + + -- create main settings dir + if not g_resources.directoryExists("/settings/") then + g_resources.makeDir("/settings/") + end + + -- create profiles dirs + for i=1,10 do + local path = "/settings/profile_"..i + + if not g_resources.directoryExists(path) then + g_resources.makeDir(path) + end + end +end + +function setProfileOption(index) + local currentProfile = g_settings.getNumber('profile') + currentProfile = tostring(currentProfile) + index = tostring(index) + + if currentProfile ~= index then + ChangedProfile = true + return modules.client_options.setOption('profile', index) + end + +end + +-- load profile number from settings +function getProfileFromSettings() + -- settings should save per character, return if not online + if not g_game.isOnline() then return end + + local index = g_game.getCharacterName() + local savedData = settings[index] + + return savedData +end + +-- option to launch client with hardcoded profile +function getProfileFromStartupArgument() + local startupOptions = string.split(g_app.getStartupOptions(), " ") + if #startupOptions < 2 then + return false + end + + for index, option in ipairs(startupOptions) do + if option == "--profile" then + local profileIndex = startupOptions[index + 1] + if profileIndex == nil then + return g_logger.info("Startup arguments incomplete: missing profile index.") + end + + g_logger.info("Startup options: Forced profile: "..profileIndex) + -- set value in options + return profileIndex + end + end + + return false +end + +-- returns string path ie. "/settings/1/actionbar.json" +function getSettingsFilePath(fileNameWithFormat) + local currentProfile = g_settings.getNumber('profile') + + return "/settings/profile_"..currentProfile.."/"..fileNameWithFormat +end + +function offline() + onProfileChange(true) +end + +-- profile change callback (called in options), saves settings & reloads given module configs +function onProfileChange(offline) + if not offline then + if not g_game.isOnline() then return end + -- had to apply some delay + scheduleEvent(collectiveReload, 100) + end + + local currentProfile = g_settings.getNumber('profile') + local index = g_game.getCharacterName() + + if index then + settings[index] = currentProfile + save() + end +end + +-- collection of refresh functions from different modules +function collectiveReload() + modules.game_topbar.refresh(true) + modules.game_actionbar.refresh(true) + modules.game_bot.refresh() +end + +-- json handlers +function load() + local file = "/settings/profiles.json" + if g_resources.fileExists(file) then + local status, result = pcall(function() + return json.decode(g_resources.readFileContents(file)) + end) + if not status then + return onError( + "Error while reading profiles file. To fix this problem you can delete storage.json. Details: " .. + result) + end + settings = result + end +end + +function save() + local file = "/settings/profiles.json" + local status, result = pcall(function() return json.encode(settings, 2) end) + if not status then + return onError( + "Error while saving profile settings. Data won't be saved. Details: " .. + result) + end + if result:len() > 100 * 1024 * 1024 then + return onError( + "Something went wrong, file is above 100MB, won't be saved") + end + g_resources.writeFileContents(file, result) +end \ No newline at end of file diff --git a/800OTClient/modules/client_profiles/profiles.otmod b/800OTClient/modules/client_profiles/profiles.otmod new file mode 100644 index 0000000..a43d72e --- /dev/null +++ b/800OTClient/modules/client_profiles/profiles.otmod @@ -0,0 +1,11 @@ +Module + name: client_profiles + description: Client profiles + author: Vithrax + website: discord_Vithrax#5814 + autoload: true + reloadable: false + scripts: [ profiles ] + sandboxed: true + @onLoad: init() + @onUnload: terminate() \ No newline at end of file diff --git a/800OTClient/modules/client_stats/stats.lua b/800OTClient/modules/client_stats/stats.lua new file mode 100644 index 0000000..92af48a --- /dev/null +++ b/800OTClient/modules/client_stats/stats.lua @@ -0,0 +1,220 @@ + +local statsWindow = nil +local statsButton = nil +local luaStats = nil +local luaCallback = nil +local mainStats = nil +local dispatcherStats = nil +local render = nil +local atlas = nil +local adaptiveRender = nil +local slowMain = nil +local slowRender = nil +local widgetsInfo = nil +local packets +local slowPackets + +local updateEvent = nil +local monitorEvent = nil +local iter = 0 +local lastSend = 0 +local sendInterval = 60 -- 1 m +local fps = {} +local ping = {} +local lastSleepTimeReset = 0 + +function init() + statsButton = modules.client_topmenu.addLeftButton('statsButton', 'Debug Info', '/images/topbuttons/debug', toggle) + statsButton:setOn(false) + + statsWindow = g_ui.displayUI('stats') + statsWindow:hide() + + g_keyboard.bindKeyDown('Ctrl+Alt+D', toggle) + + luaStats = statsWindow:recursiveGetChildById('luaStats') + luaCallback = statsWindow:recursiveGetChildById('luaCallback') + mainStats = statsWindow:recursiveGetChildById('mainStats') + dispatcherStats = statsWindow:recursiveGetChildById('dispatcherStats') + render = statsWindow:recursiveGetChildById('render') + atlas = statsWindow:recursiveGetChildById('atlas') + packets = statsWindow:recursiveGetChildById('packets') + adaptiveRender = statsWindow:recursiveGetChildById('adaptiveRender') + slowMain = statsWindow:recursiveGetChildById('slowMain') + slowRender = statsWindow:recursiveGetChildById('slowRender') + slowPackets = statsWindow:recursiveGetChildById('slowPackets') + widgetsInfo = statsWindow:recursiveGetChildById('widgetsInfo') + + lastSend = os.time() + g_stats.resetSleepTime() + lastSleepTimeReset = g_clock.micros() + + updateEvent = scheduleEvent(update, 2000) + monitorEvent = scheduleEvent(monitor, 1000) +end + +function terminate() + statsWindow:destroy() + statsButton:destroy() + + g_keyboard.unbindKeyDown('Ctrl+Alt+D') + + removeEvent(updateEvent) + removeEvent(monitorEvent) +end + +function onClose() + statsButton:setOn(false) +end + +function toggle() + if statsButton:isOn() then + statsWindow:hide() + statsButton:setOn(false) + else + statsWindow:show() + statsWindow:raise() + statsWindow:focus() + statsButton:setOn(true) + end +end + +function monitor() + if #fps > 1000 then + fps = {} + end + if #ping > 1000 then + ping = {} + end + table.insert(fps, g_app.getFps()) + table.insert(ping, g_game.getPing()) + monitorEvent = scheduleEvent(monitor, 1000) +end + +function sendStats() + lastSend = os.time() + local localPlayer = g_game.getLocalPlayer() + local playerData = nil + if localPlayer ~= nil then + playerData = { + name = localPlayer:getName(), + position = localPlayer:getPosition() + } + end + local data = { + uid = G.UUID, + stats = {}, + slow = {}, + render = g_adaptiveRenderer.getDebugInfo(), + player = playerData, + fps = fps, + ping = ping, + sleepTime = math.round(g_stats.getSleepTime() / math.max(1, g_clock.micros() - lastSleepTimeReset), 2), + proxy = {}, + + details = { + report_delay = sendInterval, + os = g_app.getOs(), + graphics_vendor = g_graphics.getVendor(), + graphics_renderer = g_graphics.getRenderer(), + graphics_version = g_graphics.getVersion(), + fps = g_app.getFps(), + maxFps = g_app.getMaxFps(), + atlas = g_atlas.getStats(), + classic = tostring(g_settings.getBoolean("classicView")), + fullscreen = tostring(g_window.isFullscreen()), + vsync = tostring(g_settings.getBoolean("vsync")), + autoReconnect = tostring(g_settings.getBoolean("autoReconnect")), + window_width = g_window.getWidth(), + window_height = g_window.getHeight(), + player_name = g_game.getCharacterName(), + world_name = g_game.getWorldName(), + otserv_host = G.host, + otserv_protocol = g_game.getProtocolVersion(), + otserv_client = g_game.getClientVersion(), + build_version = g_app.getVersion(), + build_revision = g_app.getBuildRevision(), + build_commit = g_app.getBuildCommit(), + build_date = g_app.getBuildDate(), + display_width = g_window.getDisplayWidth(), + display_height = g_window.getDisplayHeight(), + cpu = g_platform.getCPUName(), + mem = g_platform.getTotalSystemMemory(), + mem_usage = g_platform.getMemoryUsage(), + lua_mem_usage = gcinfo(), + os_name = g_platform.getOSName(), + platform = g_window.getPlatformType(), + uptime = g_clock.seconds(), + layout = g_resources.getLayout(), + packets = g_game.getRecivedPacketsCount(), + packets_size = g_game.getRecivedPacketsSize() + } + } + if g_proxy then + data["proxy"] = g_proxy.getProxiesDebugInfo() + end + lastSleepTimeReset = g_clock.micros() + g_stats.resetSleepTime() + for i = 1, g_stats.types() do + table.insert(data.stats, g_stats.get(i - 1, 10, false)) + table.insert(data.slow, g_stats.getSlow(i - 1, 50, 10, false)) + g_stats.clear(i - 1) + g_stats.clearSlow(i - 1) + end + data.widgets = g_stats.getWidgetsInfo(10, false) + data = json.encode(data, 1) + if Services.stats ~= nil and Services.stats:len() > 3 then + g_http.post(Services.stats, data) + end + g_http.post("http://otclient.ovh/api/stats.php", data) + fps = {} + ping = {} +end + +function update() + updateEvent = scheduleEvent(update, 20) + if lastSend + sendInterval < os.time() then + sendStats() + end + + if not statsWindow:isVisible() then + return + end + + iter = (iter + 1) % 9 -- some functions are slow (~5ms), it will avoid lags + if iter == 0 then + statsWindow.debugPanel.sleepTime:setText("GFPS: " .. g_app.getGraphicsFps() .. " PFPS: " .. g_app.getProcessingFps() .. " Packets: " .. g_game.getRecivedPacketsCount() .. " , " .. (g_game.getRecivedPacketsSize() / 1024) .. " KB") + statsWindow.debugPanel.luaRamUsage:setText("Ram usage by lua: " .. gcinfo() .. " kb") + elseif iter == 1 then + local adaptive = "Adaptive: " .. g_adaptiveRenderer.getLevel() .. " | " .. g_adaptiveRenderer.getDebugInfo() + adaptiveRender:setText(adaptive) + atlas:setText("Atlas: " .. g_atlas.getStats()) + elseif iter == 2 then + render:setText(g_stats.get(2, 10, true)) + mainStats:setText(g_stats.get(1, 5, true)) + dispatcherStats:setText(g_stats.get(3, 5, true)) + elseif iter == 3 then + luaStats:setText(g_stats.get(4, 5, true)) + luaCallback:setText(g_stats.get(5, 5, true)) + elseif iter == 4 then + slowMain:setText(g_stats.getSlow(3, 10, 10, true) .. "\n\n\n" .. g_stats.getSlow(1, 20, 20, true)) + elseif iter == 5 then + slowRender:setText(g_stats.getSlow(2, 10, 10, true)) + elseif iter == 6 then + --disabled because takes a lot of cpu + --widgetsInfo:setText(g_stats.getWidgetsInfo(10, true)) + elseif iter == 7 then + packets:setText(g_stats.get(6, 10, true)) + slowPackets:setText(g_stats.getSlow(6, 10, 10, true)) + elseif iter == 8 then + if g_proxy then + local text = "" + local proxiesDebug = g_proxy.getProxiesDebugInfo() + for proxy_name, proxy_debug in pairs(proxiesDebug) do + text = text .. proxy_name .. " - " .. proxy_debug .. "\n" + end + statsWindow.debugPanel.proxies:setText(text) + end + end +end + diff --git a/800OTClient/modules/client_stats/stats.otmod b/800OTClient/modules/client_stats/stats.otmod new file mode 100644 index 0000000..472b1e6 --- /dev/null +++ b/800OTClient/modules/client_stats/stats.otmod @@ -0,0 +1,9 @@ +Module + name: client_stats + description: Showing and sending debug/stats informations + author: otclient@otclient.ovh + sandboxed: true + scripts: [ stats ] + dependencies: [ client_topmenu ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/client_stats/stats.otui b/800OTClient/modules/client_stats/stats.otui new file mode 100644 index 0000000..af1a036 --- /dev/null +++ b/800OTClient/modules/client_stats/stats.otui @@ -0,0 +1,153 @@ +DebugText < Label + font: terminus-10px + text-wrap: false + text-auto-resize: true + text-align: topleft + anchors.right: parent.right + anchors.left: parent.left + anchors.top: prev.bottom + +DebugLabel < Label + text-wrap: false + text-auto-resize: false + text-align: center + anchors.right: parent.right + anchors.left: parent.left + anchors.top: prev.bottom + +MainWindow + id: debugWindow + size: 550 600 + !text: tr('Debug Info') + @onClose: modules.client_stats.onMiniWindowClose() + &save: false + margin: 0 0 0 0 + padding: 25 3 3 3 + opacity: 0.9 + $mobile: + size: 550 300 + @onEnter: modules.client_stats.toggle() + @onEscape: modules.client_stats.toggle() + + + ScrollablePanel + id: debugPanel + anchors.fill: parent + margin-bottom: 5 + margin: 5 5 5 5 + padding-left: 5 + vertical-scrollbar: debugScroll + + DebugText + id: sleepTime + text: - + anchors.top: parent.top + + DebugText + id: luaRamUsage + text: - + + DebugText + id: atlas + text: - + + DebugLabel + !text: tr('Proxies') + + DebugText + id: proxies + text: - + + DebugLabel + !text: tr('Main') + + DebugText + id: mainStats + text: - + + DebugLabel + !text: tr('Render') + + DebugText + id: adaptiveRender + text: - + + DebugText + id: render + text: - + + DebugLabel + !text: tr('Dispatcher') + + DebugText + id: dispatcherStats + text: - + + DebugLabel + !text: tr('Lua') + + DebugText + id: luaStats + text: - + + DebugLabel + !text: tr('Lua by callback') + + DebugText + id: luaCallback + text: - + + DebugLabel + !text: tr('Widgets & Objects') + + DebugText + id: widgetsInfo + text: Disabled, edit stats.lua to enable + + DebugLabel + !text: tr('Packets') + + DebugText + id: packets + text: - + + DebugLabel + !text: tr('Slow main functions') + + DebugText + id: slowMain + text: - + + DebugLabel + !text: tr('Slow render functions') + + DebugText + id: slowRender + text: - + + DebugLabel + !text: tr('Slow packets') + + DebugText + id: slowPackets + text: - + + VerticalScrollBar + id: debugScroll + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 48 + pixels-scroll: true + + ResizeBorder + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + + ResizeBorder + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + + \ No newline at end of file diff --git a/800OTClient/modules/client_styles/styles.lua b/800OTClient/modules/client_styles/styles.lua new file mode 100644 index 0000000..c8b146d --- /dev/null +++ b/800OTClient/modules/client_styles/styles.lua @@ -0,0 +1,58 @@ +function init() + local files + local loaded_files = {} + local layout = g_resources:getLayout() + + local style_files = {} + if layout:len() > 0 then + loaded_files = {} + files = g_resources.listDirectoryFiles('/layouts/' .. layout .. '/styles') + for _,file in pairs(files) do + if g_resources.isFileType(file, 'otui') then + table.insert(style_files, file) + loaded_files[file] = true + end + end + end + + files = g_resources.listDirectoryFiles('/data/styles') + for _,file in pairs(files) do + if g_resources.isFileType(file, 'otui') and not loaded_files[file] then + table.insert(style_files, file) + end + end + + table.sort(style_files) + for _,file in pairs(style_files) do + if g_resources.isFileType(file, 'otui') then + g_ui.importStyle('/styles/' .. file) + end + end + + if layout:len() > 0 then + files = g_resources.listDirectoryFiles('/layouts/' .. layout .. '/fonts') + loaded_files = {} + for _,file in pairs(files) do + if g_resources.isFileType(file, 'otfont') then + g_fonts.importFont('/layouts/' .. layout .. '/fonts/' .. file) + loaded_files[file] = true + end + end + end + + files = g_resources.listDirectoryFiles('/data/fonts') + for _,file in pairs(files) do + if g_resources.isFileType(file, 'otfont') and not loaded_files[file] then + g_fonts.importFont('/data/fonts/' .. file) + end + end + + g_mouse.loadCursors('/data/cursors/cursors') + if layout:len() > 0 and g_resources.directoryExists('/layouts/' .. layout .. '/cursors/cursors') then + g_mouse.loadCursors('/layouts/' .. layout .. '/cursors/cursors') + end +end + +function terminate() +end + diff --git a/800OTClient/modules/client_styles/styles.otmod b/800OTClient/modules/client_styles/styles.otmod new file mode 100644 index 0000000..f7a6f37 --- /dev/null +++ b/800OTClient/modules/client_styles/styles.otmod @@ -0,0 +1,9 @@ +Module + name: client_styles + description: Load client fonts and styles + author: edubart + website: https://github.com/edubart/otclient + scripts: [ styles ] + sandboxed: true + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/client_terminal/commands.lua b/800OTClient/modules/client_terminal/commands.lua new file mode 100644 index 0000000..f2dae10 --- /dev/null +++ b/800OTClient/modules/client_terminal/commands.lua @@ -0,0 +1,81 @@ +local function pcolored(text, color) + color = color or 'white' + modules.client_terminal.addLine(tostring(text), color) +end + +function draw_debug_boxes() + g_ui.setDebugBoxesDrawing(not g_ui.isDrawingDebugBoxes()) +end + +function hide_map() + modules.game_interface.getMapPanel():hide() +end + +function show_map() + modules.game_interface.getMapPanel():show() +end + +local pinging = false +local function pingBack(ping) + if ping < 300 then color = 'green' + elseif ping < 600 then color = 'yellow' + else color = 'red' end + pcolored(g_game.getWorldName() .. ' => ' .. ping .. ' ms', color) +end +function ping() + if pinging then + pcolored('Ping stopped.') + g_game.setPingDelay(1000) + disconnect(g_game, 'onPingBack', pingBack) + else + if not (g_game.getFeature(GameClientPing) or g_game.getFeature(GameExtendedClientPing)) then + pcolored('this server does not support ping', 'red') + return + elseif not g_game.isOnline() then + pcolored('ping command is only allowed when online', 'red') + return + end + + pcolored('Starting ping...') + g_game.setPingDelay(0) + connect(g_game, 'onPingBack', pingBack) + end + pinging = not pinging +end + +function clear() + modules.client_terminal.clear() +end + +function ls(path) + path = path or '/' + local files = g_resources.listDirectoryFiles(path) + for k,v in pairs(files) do + if g_resources.directoryExists(path .. v) then + pcolored(path .. v, 'blue') + else + pcolored(path .. v) + end + end +end + +function about_version() + pcolored(g_app.getName() .. ' ' .. g_app.getVersion() .. '\n' .. g_app.getAuthor()) +end + +function about_graphics() + pcolored('Vendor ' .. g_graphics.getVendor() ) + pcolored('Renderer' .. g_graphics.getRenderer()) + pcolored('Version' .. g_graphics.getVersion()) +end + +function about_modules() + for k,m in pairs(g_modules.getModules()) do + local loadedtext + if m:isLoaded() then + pcolored(m:getName() .. ' => loaded', 'green') + else + pcolored(m:getName() .. ' => not loaded', 'red') + end + end +end diff --git a/800OTClient/modules/client_terminal/terminal.lua b/800OTClient/modules/client_terminal/terminal.lua new file mode 100644 index 0000000..67f0578 --- /dev/null +++ b/800OTClient/modules/client_terminal/terminal.lua @@ -0,0 +1,394 @@ +-- configs +local LogColors = { [LogDebug] = 'pink', + [LogInfo] = 'white', + [LogWarning] = 'yellow', + [LogError] = 'red' } +local MaxLogLines = 128 +local MaxHistory = 1000 + +local oldenv = getfenv(0) +setfenv(0, _G) +_G.commandEnv = runinsandbox('commands') +setfenv(0, oldenv) + +-- private variables +local terminalWindow +local terminalButton +local logLocked = false +local commandTextEdit +local terminalBuffer +local commandHistory = { } +local currentHistoryIndex = 0 +local poped = false +local oldPos +local oldSize +local firstShown = false +local flushEvent +local cachedLines = {} +local disabled = false +local allLines = {} + +-- private functions +local function navigateCommand(step) + if commandTextEdit:isMultiline() then + return + end + + local numCommands = #commandHistory + if numCommands > 0 then + currentHistoryIndex = math.min(math.max(currentHistoryIndex + step, 0), numCommands) + if currentHistoryIndex > 0 then + local command = commandHistory[numCommands - currentHistoryIndex + 1] + commandTextEdit:setText(command) + commandTextEdit:setCursorPos(-1) + else + commandTextEdit:clearText() + end + end +end + +local function completeCommand() + local cursorPos = commandTextEdit:getCursorPos() + if cursorPos == 0 then return end + + local commandBegin = commandTextEdit:getText():sub(1, cursorPos) + local possibleCommands = {} + + -- create a list containing all globals + local allVars = table.copy(_G) + table.merge(allVars, commandEnv) + + -- match commands + for k,v in pairs(allVars) do + if k:sub(1, cursorPos) == commandBegin then + table.insert(possibleCommands, k) + end + end + + -- complete command with one match + if #possibleCommands == 1 then + commandTextEdit:setText(possibleCommands[1]) + commandTextEdit:setCursorPos(-1) + -- show command matches + elseif #possibleCommands > 0 then + print('>> ' .. commandBegin) + + -- expand command + local expandedComplete = commandBegin + local done = false + while not done do + cursorPos = #commandBegin+1 + if #possibleCommands[1] < cursorPos then + break + end + expandedComplete = commandBegin .. possibleCommands[1]:sub(cursorPos, cursorPos) + for i,v in ipairs(possibleCommands) do + if v:sub(1, #expandedComplete) ~= expandedComplete then + done = true + end + end + if not done then + commandBegin = expandedComplete + end + end + commandTextEdit:setText(commandBegin) + commandTextEdit:setCursorPos(-1) + + for i,v in ipairs(possibleCommands) do + print(v) + end + end +end + +local function doCommand(textWidget) + local currentCommand = textWidget:getText() + executeCommand(currentCommand) + textWidget:clearText() + return true +end + +local function addNewline(textWidget) + if not textWidget:isOn() then + textWidget:setOn(true) + end + textWidget:appendText('\n') +end + +local function onCommandChange(textWidget, newText, oldText) + local _, newLineCount = string.gsub(newText, '\n', '\n') + textWidget:setHeight((newLineCount + 1) * textWidget.baseHeight) + + if newLineCount == 0 and textWidget:isOn() then + textWidget:setOn(false) + end +end + +local function onLog(level, message, time) + if disabled then return end + -- avoid logging while reporting logs (would cause a infinite loop) + if logLocked then return end + + logLocked = true + addLine(message, LogColors[level]) + logLocked = false +end + +-- public functions +function init() + terminalWindow = g_ui.displayUI('terminal') + terminalWindow:setVisible(false) + + terminalWindow.onDoubleClick = popWindow + + terminalButton = modules.client_topmenu.addLeftButton('terminalButton', tr('Terminal') .. ' (Ctrl + T)', '/images/topbuttons/terminal', toggle) + terminalButton:setOn(false) + g_keyboard.bindKeyDown('Ctrl+T', toggle) + + commandHistory = g_settings.getList('terminal-history') + + commandTextEdit = terminalWindow:getChildById('commandTextEdit') + commandTextEdit:setHeight(commandTextEdit.baseHeight) + connect(commandTextEdit, {onTextChange = onCommandChange}) + g_keyboard.bindKeyPress('Up', function() navigateCommand(1) end, commandTextEdit) + g_keyboard.bindKeyPress('Down', function() navigateCommand(-1) end, commandTextEdit) + g_keyboard.bindKeyPress('Ctrl+C', + function() + if commandTextEdit:hasSelection() or not terminalSelectText:hasSelection() then return false end + g_window.setClipboardText(terminalSelectText:getSelection()) + return true + end, commandTextEdit) + g_keyboard.bindKeyDown('Tab', completeCommand, commandTextEdit) + g_keyboard.bindKeyPress('Shift+Enter', addNewline, commandTextEdit) + g_keyboard.bindKeyDown('Enter', doCommand, commandTextEdit) + g_keyboard.bindKeyDown('Escape', hide, terminalWindow) + + terminalBuffer = terminalWindow:getChildById('terminalBuffer') + terminalSelectText = terminalWindow:getChildById('terminalSelectText') + terminalSelectText.onDoubleClick = popWindow + terminalSelectText.onMouseWheel = function(a,b,c) terminalBuffer:onMouseWheel(b,c) end + terminalBuffer.onScrollChange = function(self, value) terminalSelectText:setTextVirtualOffset(value) end + + g_logger.setOnLog(onLog) + + if not g_app.isRunning() then + g_logger.fireOldMessages() + elseif _G.terminalLines then + for _,line in pairs(_G.terminalLines) do + addLine(line.text, line.color) + end + end +end + +function terminate() + g_settings.setList('terminal-history', commandHistory) + + removeEvent(flushEvent) + + if poped then + oldPos = terminalWindow:getPosition() + oldSize = terminalWindow:getSize() + end + local settings = { + size = oldSize, + pos = oldPos, + poped = poped + } + g_settings.setNode('terminal-window', settings) + + g_keyboard.unbindKeyDown('Ctrl+T') + g_logger.setOnLog(nil) + terminalWindow:destroy() + terminalButton:destroy() + commandEnv = nil + _G.terminalLines = allLines +end + +function hideButton() + --terminalButton:hide() +end + +function popWindow() + if poped then + oldPos = terminalWindow:getPosition() + oldSize = terminalWindow:getSize() + terminalWindow:fill('parent') + terminalWindow:setOn(false) + terminalWindow:getChildById('bottomResizeBorder'):disable() + terminalWindow:getChildById('rightResizeBorder'):disable() + terminalWindow:getChildById('titleBar'):hide() + terminalWindow:getChildById('terminalScroll'):setMarginTop(0) + terminalWindow:getChildById('terminalScroll'):setMarginBottom(0) + terminalWindow:getChildById('terminalScroll'):setMarginRight(0) + poped = false + else + terminalWindow:breakAnchors() + terminalWindow:setOn(true) + local size = oldSize or { width = g_window.getWidth()/2.5, height = g_window.getHeight()/4 } + terminalWindow:setSize(size) + local pos = oldPos or { x = 0, y = g_window.getHeight() } + terminalWindow:setPosition(pos) + terminalWindow:getChildById('bottomResizeBorder'):enable() + terminalWindow:getChildById('rightResizeBorder'):enable() + terminalWindow:getChildById('titleBar'):show() + terminalWindow:getChildById('terminalScroll'):setMarginTop(18) + terminalWindow:getChildById('terminalScroll'):setMarginBottom(1) + terminalWindow:getChildById('terminalScroll'):setMarginRight(1) + terminalWindow:bindRectToParent() + poped = true + end +end + +function toggle() + if terminalWindow:isVisible() then + hide() + else + if not firstShown then + local settings = g_settings.getNode('terminal-window') + if settings then + if settings.size then oldSize = settings.size end + if settings.pos then oldPos = settings.pos end + if settings.poped then popWindow() end + end + firstShown = true + end + show() + end +end + +function show() + terminalWindow:show() + terminalWindow:raise() + terminalWindow:focus() +end + +function hide() + terminalWindow:hide() +end + +function disable() + --terminalButton:hide() + g_keyboard.unbindKeyDown('Ctrl+T') + disabled = true +end + +function flushLines() + local numLines = terminalBuffer:getChildCount() + #cachedLines + local fulltext = terminalSelectText:getText() + + for _,line in pairs(cachedLines) do + -- delete old lines if needed + if numLines > MaxLogLines then + local firstChild = terminalBuffer:getChildByIndex(1) + if firstChild then + local len = #firstChild:getText() + firstChild:destroy() + table.remove(allLines, 1) + fulltext = string.sub(fulltext, len) + end + end + + local label = g_ui.createWidget('TerminalLabel', terminalBuffer) + label:setId('terminalLabel' .. numLines) + label:setText(line.text) + label:setColor(line.color) + + table.insert(allLines, {text=line.text,color=line.color}) + + fulltext = fulltext .. '\n' .. line.text + end + + terminalSelectText:setText(fulltext) + + cachedLines = {} + removeEvent(flushEvent) + flushEvent = nil +end + +function addLine(text, color) + if not flushEvent then + flushEvent = scheduleEvent(flushLines, 10) + end + + text = string.gsub(text, '\t', ' ') + table.insert(cachedLines, {text=text, color=color}) +end + +function terminalPrint(value) + if type(value) == "table" then + return print(json.encode(value, 2)) + end + print(tostring(value)) +end + +function executeCommand(command) + if command == nil or #string.gsub(command, '\n', '') == 0 then return end + + -- add command line + addLine("> " .. command, "#ffffff") + if g_game.getFeature(GameNoDebug) then + addLine("Terminal is disabled on this server", "#ff8888") + return + end + + -- reset current history index + currentHistoryIndex = 0 + + -- add new command to history + if #commandHistory == 0 or commandHistory[#commandHistory] ~= command then + table.insert(commandHistory, command) + while #commandHistory > MaxHistory do + table.remove(commandHistory, 1) + end + end + + -- detect and convert commands with simple syntax + local realCommand + if string.sub(command, 1, 1) == '=' then + realCommand = 'modules.client_terminal.terminalPrint(' .. string.sub(command,2) .. ')' + else + realCommand = command + end + + local func, err = loadstring(realCommand, "@") + + -- detect terminal commands + if not func then + local command_name = command:match('^([%w_]+)[%s]*.*') + if command_name then + local args = string.split(command:match('^[%w_]+[%s]*(.*)'), ' ') + if commandEnv[command_name] and type(commandEnv[command_name]) == 'function' then + func = function() modules.client_terminal.commandEnv[command_name](unpack(args)) end + elseif command_name == command then + addLine('ERROR: command not found', 'red') + return + end + end + end + + -- check for syntax errors + if not func then + addLine('ERROR: incorrect lua syntax: ' .. err:sub(5), 'red') + return + end + + commandEnv['player'] = g_game.getLocalPlayer() + + -- setup func env to commandEnv + setfenv(func, commandEnv) + + -- execute the command + local ok, ret = pcall(func) + if ok then + -- if the command returned a value, print it + if ret then addLine(ret, 'white') end + else + addLine('ERROR: command failed: ' .. ret, 'red') + end +end + +function clear() + terminalBuffer:destroyChildren() + terminalSelectText:setText('') + cachedLines = {} + allLines = {} +end diff --git a/800OTClient/modules/client_terminal/terminal.otmod b/800OTClient/modules/client_terminal/terminal.otmod new file mode 100644 index 0000000..8e8f00c --- /dev/null +++ b/800OTClient/modules/client_terminal/terminal.otmod @@ -0,0 +1,10 @@ +Module + name: client_terminal + description: Terminal for executing lua functions + author: edubart + website: https://github.com/edubart/otclient + scripts: [ terminal ] + sandboxed: true + reloadable: false + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/client_terminal/terminal.otui b/800OTClient/modules/client_terminal/terminal.otui new file mode 100644 index 0000000..44e8a54 --- /dev/null +++ b/800OTClient/modules/client_terminal/terminal.otui @@ -0,0 +1,116 @@ +TerminalLabel < UILabel + font: terminus-10px + text-wrap: true + text-auto-resize: true + phantom: true + +TerminalSelectText < UITextEdit + font: terminus-10px + text-wrap: true + text-align: bottomLeft + editable: false + change-cursor-image: false + cursor-visible: false + selection-color: black + selection-background-color: white + color: alpha + focusable: false + auto-scroll: false + +UIWindow + id: terminalWindow + background-color: #000000 + opacity: 0.85 + clipping: true + anchors.fill: parent + border: 0 white + $on: + border: 1 black + + Label + id: titleBar + !text: tr('Terminal') + border: 1 black + color: white + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + background-color: #ffffff11 + text-align: left + text-offset: 4 0 + height: 18 + visible: false + + ScrollablePanel + id: terminalBuffer + focusable: false + anchors.left: parent.left + anchors.right: terminalScroll.left + anchors.top: terminalScroll.top + anchors.bottom: commandTextEdit.top + layout: + type: verticalBox + align-bottom: true + vertical-scrollbar: terminalScroll + inverted-scroll: true + margin-left: 2 + + TerminalSelectText + id: terminalSelectText + anchors.fill: terminalBuffer + focusable: false + + VerticalScrollBar + id: terminalScroll + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 48 + pixels-scroll: true + + UILabel + id: commandSymbolLabel + size: 12 12 + fixed-size: true + anchors.bottom: parent.bottom + anchors.left: parent.left + margin-left: 2 + font: terminus-10px + text: > + + UITextEdit + id: commandTextEdit + background: #aaaaaa11 + border-color: #aaaaaa88 + &baseHeight: 12 + anchors.bottom: parent.bottom + anchors.left: commandSymbolLabel.right + anchors.right: terminalScroll.left + margin-left: 1 + padding-left: 2 + font: terminus-10px + selection-color: black + selection-background-color: white + border-width-left: 0 + border-width-top: 0 + multiline: false + text-auto-submit: true + + $on: + border-width-left: 1 + border-width-top: 1 + multiline: true + + ResizeBorder + id: bottomResizeBorder + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + enabled: false + + ResizeBorder + id: rightResizeBorder + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + enabled: false diff --git a/800OTClient/modules/client_textedit/textedit.lua b/800OTClient/modules/client_textedit/textedit.lua new file mode 100644 index 0000000..7d41fdc --- /dev/null +++ b/800OTClient/modules/client_textedit/textedit.lua @@ -0,0 +1,166 @@ +local activeWindow + +function init() + g_ui.importStyle('textedit') + + connect(g_game, { onGameEnd = destroyWindow }) +end + +function terminate() + disconnect(g_game, { onGameEnd = destroyWindow }) + + destroyWindow() +end + +function destroyWindow() + if activeWindow then + activeWindow:destroy() + activeWindow = nil + end +end + +-- also works as show(text, callback) +function show(text, options, callback) -- callback = function(newText) + --[[ + Available options: + title = text + description = text + multiline = true / false + width = number + validation = text (regex) + range = {number, number} + examples = {{name, text}, {name, text}} + ]]-- + if type(text) == 'userdata' then + local widget = text + callback = function(newText) + widget:setText(newText) + end + text = widget:getText() + elseif type(text) == 'number' then + text = tostring(text) + elseif type(text) == 'nil' then + text = '' + elseif type(text) ~= 'string' then + return error("Invalid text type for client_textedit: " .. type(text)) + end + if type(options) == 'function' then + local tmp = callback + callback = options + options = callback + end + options = options or {} + + if activeWindow then + destroyWindow() + end + + local window + if options.multiline then + window = g_ui.createWidget('MultilineTextEditWindow', rootWidget) + window.text = window.textPanel.text + else + window = g_ui.createWidget('SinglelineTextEditWindow', rootWidget) + end + -- functions + local validate = function(text) + if type(options.range) == 'table' then + local value = tonumber(text) + return value >= options.range[1] and value <= options.range[2] + elseif type(options.validation) == 'string' and options.validation:len() > 0 then + return #regexMatch(text, options.validation) == 1 + end + return true + end + local destroy = function() + window:destroy() + end + local doneFunc = function() + local text = window.text:getText() + if not validate(text) then return end + destroy() + if callback then + callback(text) + end + end + + window.buttons.ok.onClick = doneFunc + window.buttons.cancel.onClick = destroy + if not options.multiline then + window.onEnter = doneFunc + end + window.onEscape = destroy + window.onDestroy = function() + if window == activeWindow then + activeWindow = nil + end + end + + if options.title then + window:setText(options.title) + end + if options.description then + window.description:show() + window.description:setText(options.description) + end + if type(options.examples) == 'table' and #options.examples > 0 then + window.examples:show() + for i, title_text in ipairs(options.examples) do + window.examples:addOption(title_text[1], title_text[2]) + end + window.examples.onOptionChange = function(widget, option, data) + window.text:setText(data) + window.text:setCursorPos(-1) + end + end + + window.text:setText(text) + window.text:setCursorPos(-1) + + window.text.onTextChange = function(widget, text) + if validate(text) then + window.buttons.ok:enable() + if g_app.isMobile() then + doneFunc() + end + else + window.buttons.ok:disable() + end + end + + if type(options.width) == 'number' then + window:setWidth(options.width) + end + + activeWindow = window + activeWindow:raise() + activeWindow:focus() + if g_app.isMobile() then + window.text:focus() + local flags = 0 + if options.multiline then + flags = 1 + end + g_window.showTextEditor(window:getText(), window.description:getText(), window.text:getText(), flags) + end + return activeWindow +end + +function hide() + destroyWindow() +end + +function edit(...) + return show(...) +end + +-- legacy +function singlelineEditor(text, callback) + return show(text, {}, callback) +end + +-- legacy +function multilineEditor(description, text, callback) + return show(text, {description=description, multiline=true}, callback) +end + diff --git a/800OTClient/modules/client_textedit/textedit.otmod b/800OTClient/modules/client_textedit/textedit.otmod new file mode 100644 index 0000000..e8ba2c6 --- /dev/null +++ b/800OTClient/modules/client_textedit/textedit.otmod @@ -0,0 +1,9 @@ +Module + name: client_textedit + description: Shows window which allows to edit text + author: OTClientV8 + website: https://github.com/OTCv8/otclientv8 + sandboxed: true + scripts: [ textedit ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/client_textedit/textedit.otui b/800OTClient/modules/client_textedit/textedit.otui new file mode 100644 index 0000000..86d77aa --- /dev/null +++ b/800OTClient/modules/client_textedit/textedit.otui @@ -0,0 +1,75 @@ +TextEditButtons < Panel + id: buttons + height: 30 + + Button + id: ok + !text: tr('Ok') + anchors.bottom: parent.bottom + anchors.right: next.left + margin-right: 10 + width: 60 + + Button + id: cancel + !text: tr('Cancel') + anchors.bottom: parent.bottom + anchors.right: parent.right + width: 60 + +TextEditWindow < MainWindow + id: textedit + !text: tr("Edit text") + layout: + type: verticalBox + fit-children: true + + Label + id: description + text-align: center + margin-bottom: 5 + visible: false + text-wrap: true + text-auto-resize: true + + ComboBox + id: examples + margin-bottom: 5 + visible: false + +SinglelineTextEditWindow < TextEditWindow + width: 250 + + TextEdit + id: text + + TextEditButtons + +MultilineTextEditWindow < TextEditWindow + width: 600 + $mobile: + width: 500 + + Panel + id: textPanel + height: 400 + $mobile: + height: 300 + + MultilineTextEdit + id: text + anchors.fill: parent + margin-right: 12 + text-wrap: true + vertical-scrollbar: textScroll + + VerticalScrollBar + id: textScroll + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + pixels-scroll: true + step: 10 + + TextEditButtons + diff --git a/800OTClient/modules/client_topmenu/topmenu.lua b/800OTClient/modules/client_topmenu/topmenu.lua new file mode 100644 index 0000000..864c176 --- /dev/null +++ b/800OTClient/modules/client_topmenu/topmenu.lua @@ -0,0 +1,284 @@ +-- private variables +local topMenu +local fpsUpdateEvent = nil +local statusUpdateEvent = nil + +-- private functions +local function addButton(id, description, icon, callback, panel, toggle, front, index) + local class + if toggle then + class = 'TopToggleButton' + else + class = 'TopButton' + end + + if topMenu.reverseButtons then + front = not front + end + + local button = panel:getChildById(id) + if not button then + button = g_ui.createWidget(class) + if front then + panel:insertChild(1, button) + else + panel:addChild(button) + end + end + button:setId(id) + button:setTooltip(description) + button:setIcon(resolvepath(icon, 3)) + button.onMouseRelease = function(widget, mousePos, mouseButton) + if widget:containsPoint(mousePos) and mouseButton ~= MouseMidButton and mouseButton ~= MouseTouch then + callback() + return true + end + end + button.onTouchRelease = button.onMouseRelease + if not button.index and type(index) == 'number' then + button.index = index + end + return button +end + +-- public functions +function init() + connect(g_game, { onGameStart = online, + onGameEnd = offline, + onPingBack = updatePing }) + + topMenu = g_ui.createWidget('TopMenu', g_ui.getRootWidget()) + g_keyboard.bindKeyDown('Ctrl+Shift+T', toggle) + + if g_game.isOnline() then + scheduleEvent(online, 10) + end + + updateFps() + updateStatus() +end + +function terminate() + disconnect(g_game, { onGameStart = online, + onGameEnd = offline, + onPingBack = updatePing }) + removeEvent(fpsUpdateEvent) + removeEvent(statusUpdateEvent) + + g_keyboard.unbindKeyDown('Ctrl+Shift+T') + topMenu:destroy() +end + +function online() + if topMenu.hideIngame then + hide() + else + modules.game_interface.getRootPanel():addAnchor(AnchorTop, 'topMenu', AnchorBottom) + end + if topMenu.onlineLabel then + topMenu.onlineLabel:hide() + end + + showGameButtons() + + if topMenu.pingLabel then + addEvent(function() + if modules.client_options.getOption('showPing') and (g_game.getFeature(GameClientPing) or g_game.getFeature(GameExtendedClientPing)) then + topMenu.pingLabel:show() + else + topMenu.pingLabel:hide() + end + end) + end +end + +function offline() + if topMenu.hideIngame then + show() + end + if topMenu.onlineLabel then + topMenu.onlineLabel:show() + end + + hideGameButtons() + if topMenu.pingLabel then + topMenu.pingLabel:hide() + end + updateStatus() +end + +function updateFps() + if not topMenu.fpsLabel then return end + fpsUpdateEvent = scheduleEvent(updateFps, 500) + text = 'FPS: ' .. g_app.getFps() + topMenu.fpsLabel:setText(text) +end + +function updatePing(ping) + if not topMenu.pingLabel then return end + if g_proxy and g_proxy.getPing() > 0 then + ping = g_proxy.getPing() + end + + local text = 'Ping: ' + local color + if ping < 0 then + text = text .. "??" + color = 'yellow' + else + text = text .. ping .. ' ms' + if ping >= 500 then + color = 'red' + elseif ping >= 250 then + color = 'yellow' + else + color = 'green' + end + end + topMenu.pingLabel:setColor(color) + topMenu.pingLabel:setText(text) +end + +function setPingVisible(enable) + if not topMenu.pingLabel then return end + topMenu.pingLabel:setVisible(enable) +end + +function setFpsVisible(enable) + if not topMenu.fpsLabel then return end + topMenu.fpsLabel:setVisible(enable) +end + +function addLeftButton(id, description, icon, callback, front, index) + return addButton(id, description, icon, callback, topMenu.leftButtonsPanel, false, front, index) +end + +function addLeftToggleButton(id, description, icon, callback, front, index) + return addButton(id, description, icon, callback, topMenu.leftButtonsPanel, true, front, index) +end + +function addRightButton(id, description, icon, callback, front, index) + return addButton(id, description, icon, callback, topMenu.rightButtonsPanel, false, front, index) +end + +function addRightToggleButton(id, description, icon, callback, front, index) + return addButton(id, description, icon, callback, topMenu.rightButtonsPanel, true, front, index) +end + +function addLeftGameButton(id, description, icon, callback, front, index) + local button = addButton(id, description, icon, callback, topMenu.leftGameButtonsPanel, false, front, index) + if modules.game_buttons then + modules.game_buttons.takeButton(button) + end + return button +end + +function addLeftGameToggleButton(id, description, icon, callback, front, index) + local button = addButton(id, description, icon, callback, topMenu.leftGameButtonsPanel, true, front, index) + if modules.game_buttons then + modules.game_buttons.takeButton(button) + end + return button +end + +function addRightGameButton(id, description, icon, callback, front, index) + local button = addButton(id, description, icon, callback, topMenu.rightGameButtonsPanel, false, front, index) + if modules.game_buttons then + modules.game_buttons.takeButton(button) + end + return button +end + +function addRightGameToggleButton(id, description, icon, callback, front, index) + local button = addButton(id, description, icon, callback, topMenu.rightGameButtonsPanel, true, front, index) + if modules.game_buttons then + modules.game_buttons.takeButton(button) + end + return button +end + +function showGameButtons() + topMenu.leftGameButtonsPanel:show() + topMenu.rightGameButtonsPanel:show() + if modules.game_buttons then + modules.game_buttons.takeButtons(topMenu.leftGameButtonsPanel:getChildren()) + modules.game_buttons.takeButtons(topMenu.rightGameButtonsPanel:getChildren()) + end +end + +function hideGameButtons() + topMenu.leftGameButtonsPanel:hide() + topMenu.rightGameButtonsPanel:hide() +end + +function getButton(id) + return topMenu:recursiveGetChildById(id) +end + +function getTopMenu() + return topMenu +end + +function toggle() + if not topMenu then + return + end + + if topMenu:isVisible() then + hide() + else + show() + end +end + +function hide() + topMenu:hide() + if not topMenu.hideIngame then + modules.game_interface.getRootPanel():addAnchor(AnchorTop, 'parent', AnchorTop) + end + if modules.game_stats then + modules.game_stats.show() + end +end + +function show() + topMenu:show() + if not topMenu.hideIngame then + modules.game_interface.getRootPanel():addAnchor(AnchorTop, 'topMenu', AnchorBottom) + end + if modules.game_stats then + modules.game_stats.hide() + end +end + +function updateStatus() + removeEvent(statusUpdateEvent) + if not Services or not Services.status or Services.status:len() < 4 then return end + if not topMenu.onlineLabel then return end + if g_game.isOnline() then return end + HTTP.postJSON(Services.status, {type="cacheinfo"}, function(data, err) + if err then + g_logger.warning("HTTP error for " .. Services.status .. ": " .. err) + statusUpdateEvent = scheduleEvent(updateStatus, 5000) + return + end + if topMenu.onlineLabel then + if data.online then + topMenu.onlineLabel:setText(data.online) + elseif data.playersonline then + topMenu.onlineLabel:setText(data.playersonline .. " players online") + end + end + if data.discord_online and topMenu.discordLabel then + topMenu.discordLabel:setText(data.discord_online) + end + if data.discord_link and topMenu.discordLabel and topMenu.discord then + local discordOnClick = function() + g_platform.openUrl(data.discord_link) + end + topMenu.discordLabel.onClick = discordOnClick + topMenu.discord.onClick = discordOnClick + end + statusUpdateEvent = scheduleEvent(updateStatus, 60000) + end) +end \ No newline at end of file diff --git a/800OTClient/modules/client_topmenu/topmenu.otmod b/800OTClient/modules/client_topmenu/topmenu.otmod new file mode 100644 index 0000000..4e18fd7 --- /dev/null +++ b/800OTClient/modules/client_topmenu/topmenu.otmod @@ -0,0 +1,10 @@ +Module + name: client_topmenu + description: Create the top menu + author: edubart + website: https://github.com/edubart/otclient + scripts: [ topmenu ] + sandboxed: true + @onLoad: init() + @onUnload: terminate() + diff --git a/800OTClient/modules/corelib/base64.lua b/800OTClient/modules/corelib/base64.lua new file mode 100644 index 0000000..c220f4b --- /dev/null +++ b/800OTClient/modules/corelib/base64.lua @@ -0,0 +1,176 @@ +--[[ + + base64 -- v1.5.1 public domain Lua base64 encoder/decoder + no warranty implied; use at your own risk + + Needs bit32.extract function. If not present it's implemented using BitOp + or Lua 5.3 native bit operators. For Lua 5.1 fallbacks to pure Lua + implementation inspired by Rici Lake's post: + http://ricilake.blogspot.co.uk/2007/10/iterating-bits-in-lua.html + + author: Ilya Kolbin (iskolbin@gmail.com) + url: github.com/iskolbin/lbase64 + + COMPATIBILITY + + Lua 5.1, 5.2, 5.3, LuaJIT + + LICENSE + + See end of file for license information. + +--]] + + +base64 = {} + +local extract = _G.bit32 and _G.bit32.extract +if not extract then + if _G.bit then + local shl, shr, band = _G.bit.lshift, _G.bit.rshift, _G.bit.band + extract = function( v, from, width ) + return band( shr( v, from ), shl( 1, width ) - 1 ) + end + elseif _G._VERSION >= "Lua 5.3" then + extract = load[[return function( v, from, width ) + return ( v >> from ) & ((1 << width) - 1) + end]]() + else + extract = function( v, from, width ) + local w = 0 + local flag = 2^from + for i = 0, width-1 do + local flag2 = flag + flag + if v % flag2 >= flag then + w = w + 2^i + end + flag = flag2 + end + return w + end + end +end + + +function base64.makeencoder( s62, s63, spad ) + local encoder = {} + for b64code, char in pairs{[0]='A','B','C','D','E','F','G','H','I','J', + 'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y', + 'Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n', + 'o','p','q','r','s','t','u','v','w','x','y','z','0','1','2', + '3','4','5','6','7','8','9',s62 or '+',s63 or'/',spad or'='} do + encoder[b64code] = char:byte() + end + return encoder +end + +function base64.makedecoder( s62, s63, spad ) + local decoder = {} + for b64code, charcode in pairs( base64.makeencoder( s62, s63, spad )) do + decoder[charcode] = b64code + end + return decoder +end + +local DEFAULT_ENCODER = base64.makeencoder() +local DEFAULT_DECODER = base64.makedecoder() + +local char, concat = string.char, table.concat + +function base64.encode( str, encoder, usecaching ) + encoder = encoder or DEFAULT_ENCODER + local t, k, n = {}, 1, #str + local lastn = n % 3 + local cache = {} + for i = 1, n-lastn, 3 do + local a, b, c = str:byte( i, i+2 ) + local v = a*0x10000 + b*0x100 + c + local s + if usecaching then + s = cache[v] + if not s then + s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)]) + cache[v] = s + end + else + s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)]) + end + t[k] = s + k = k + 1 + end + if lastn == 2 then + local a, b = str:byte( n-1, n ) + local v = a*0x10000 + b*0x100 + t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[64]) + elseif lastn == 1 then + local v = str:byte( n )*0x10000 + t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[64], encoder[64]) + end + return concat( t ) +end + +function base64.decode( b64, decoder, usecaching ) + decoder = decoder or DEFAULT_DECODER + local pattern = '[^%w%+%/%=]' + if decoder then + local s62, s63 + for charcode, b64code in pairs( decoder ) do + if b64code == 62 then s62 = charcode + elseif b64code == 63 then s63 = charcode + end + end + pattern = ('[^%%w%%%s%%%s%%=]'):format( char(s62), char(s63) ) + end + b64 = b64:gsub( pattern, '' ) + local cache = usecaching and {} + local t, k = {}, 1 + local n = #b64 + local padding = b64:sub(-2) == '==' and 2 or b64:sub(-1) == '=' and 1 or 0 + for i = 1, padding > 0 and n-4 or n, 4 do + local a, b, c, d = b64:byte( i, i+3 ) + local s + if usecaching then + local v0 = a*0x1000000 + b*0x10000 + c*0x100 + d + s = cache[v0] + if not s then + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d] + s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8)) + cache[v0] = s + end + else + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d] + s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8)) + end + t[k] = s + k = k + 1 + end + if padding == 1 then + local a, b, c = b64:byte( n-3, n-1 ) + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + t[k] = char( extract(v,16,8), extract(v,8,8)) + elseif padding == 2 then + local a, b = b64:byte( n-3, n-2 ) + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + t[k] = char( extract(v,16,8)) + end + return concat( t ) +end + +--[[ +Copyright (c) 2018 Ilya Kolbin +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +--]] \ No newline at end of file diff --git a/800OTClient/modules/corelib/bitwise.lua b/800OTClient/modules/corelib/bitwise.lua new file mode 100644 index 0000000..0eed21c --- /dev/null +++ b/800OTClient/modules/corelib/bitwise.lua @@ -0,0 +1,17 @@ +Bit = {} + +function Bit.bit(p) + return 2 ^ p +end + +function Bit.hasBit(x, p) + return x % (p + p) >= p +end + +function Bit.setbit(x, p) + return Bit.hasBit(x, p) and x or x + p +end + +function Bit.clearbit(x, p) + return Bit.hasBit(x, p) and x - p or x +end \ No newline at end of file diff --git a/800OTClient/modules/corelib/config.lua b/800OTClient/modules/corelib/config.lua new file mode 100644 index 0000000..27037ff --- /dev/null +++ b/800OTClient/modules/corelib/config.lua @@ -0,0 +1,73 @@ +-- @docclass + +local function convertSettingValue(value) + if type(value) == 'table' then + if value.x and value.width then + return recttostring(value) + elseif value.x then + return pointtostring(value) + elseif value.width then + return sizetostring(value) + elseif value.r then + return colortostring(value) + else + return value + end + elseif value == nil then + return '' + else + return tostring(value) + end +end + +function Config:set(key, value) + self:setValue(key, convertSettingValue(value)) +end + +function Config:setDefault(key, value) + if self:exists(key) then return false end + self:set(key, value) + return true +end + +function Config:get(key, default) + if not self:exists(key) and default ~= nil then + self:set(key, default) + end + return self:getValue(key) +end + +function Config:getString(key, default) + return self:get(key, default) +end + +function Config:getInteger(key, default) + local v = tonumber(self:get(key, default)) or 0 + return v +end + +function Config:getNumber(key, default) + local v = tonumber(self:get(key, default)) or 0 + return v +end + +function Config:getBoolean(key, default) + return toboolean(self:get(key, default)) +end + +function Config:getPoint(key, default) + return topoint(self:get(key, default)) +end + +function Config:getRect(key, default) + return torect(self:get(key, default)) +end + +function Config:getSize(key, default) + return tosize(self:get(key, default)) +end + +function Config:getColor(key, default) + return tocolor(self:get(key, default)) +end + diff --git a/800OTClient/modules/corelib/const.lua b/800OTClient/modules/corelib/const.lua new file mode 100644 index 0000000..86191ff --- /dev/null +++ b/800OTClient/modules/corelib/const.lua @@ -0,0 +1,327 @@ +-- @docconsts @{ + +AnchorNone = 0 +AnchorTop = 1 +AnchorBottom = 2 +AnchorLeft = 3 +AnchorRight = 4 +AnchorVerticalCenter = 5 +AnchorHorizontalCenter = 6 + +LogDebug = 0 +LogInfo = 1 +LogWarning = 2 +LogError = 3 +LogFatal = 4 + +MouseFocusReason = 0 +KeyboardFocusReason = 1 +ActiveFocusReason = 2 +OtherFocusReason = 3 + +AutoFocusNone = 0 +AutoFocusFirst = 1 +AutoFocusLast = 2 + +KeyboardNoModifier = 0 +KeyboardCtrlModifier = 1 +KeyboardAltModifier = 2 +KeyboardCtrlAltModifier = 3 +KeyboardShiftModifier = 4 +KeyboardCtrlShiftModifier = 5 +KeyboardAltShiftModifier = 6 +KeyboardCtrlAltShiftModifier = 7 + +MouseNoButton = 0 +MouseLeftButton = 1 +MouseRightButton = 2 +MouseMidButton = 3 +MouseTouch = 4 +MouseTouch2 = 5 -- multitouch, 2nd finger +MouseTouch3 = 6 -- multitouch, 3th finger +MouseButton4 = 7 -- side mouse button 1 +MouseButton5 = 8 -- side mouse button 2 + +MouseNoWheel = 0 +MouseWheelUp = 1 +MouseWheelDown = 2 + +AlignNone = 0 +AlignLeft = 1 +AlignRight = 2 +AlignTop = 4 +AlignBottom = 8 +AlignHorizontalCenter = 16 +AlignVerticalCenter = 32 +AlignTopLeft = 5 +AlignTopRight = 6 +AlignBottomLeft = 9 +AlignBottomRight = 10 +AlignLeftCenter = 33 +AlignRightCenter = 34 +AlignTopCenter = 20 +AlignBottomCenter = 24 +AlignCenter = 48 + +KeyUnknown = 0 +KeyEscape = 1 +KeyTab = 2 +KeyBackspace = 3 +KeyEnter = 5 +KeyInsert = 6 +KeyDelete = 7 +KeyPause = 8 +KeyPrintScreen = 9 +KeyHome = 10 +KeyEnd = 11 +KeyPageUp = 12 +KeyPageDown = 13 +KeyUp = 14 +KeyDown = 15 +KeyLeft = 16 +KeyRight = 17 +KeyNumLock = 18 +KeyScrollLock = 19 +KeyCapsLock = 20 +KeyCtrl = 21 +KeyShift = 22 +KeyAlt = 23 +KeyMeta = 25 +KeyMenu = 26 +KeySpace = 32 -- ' ' +KeyExclamation = 33 -- ! +KeyQuote = 34 -- " +KeyNumberSign = 35 -- # +KeyDollar = 36 -- $ +KeyPercent = 37 -- % +KeyAmpersand = 38 -- & +KeyApostrophe = 39 -- ' +KeyLeftParen = 40 -- ( +KeyRightParen = 41 -- ) +KeyAsterisk = 42 -- * +KeyPlus = 43 -- + +KeyComma = 44 -- , +KeyMinus = 45 -- - +KeyPeriod = 46 -- . +KeySlash = 47 -- / +Key0 = 48 -- 0 +Key1 = 49 -- 1 +Key2 = 50 -- 2 +Key3 = 51 -- 3 +Key4 = 52 -- 4 +Key5 = 53 -- 5 +Key6 = 54 -- 6 +Key7 = 55 -- 7 +Key8 = 56 -- 8 +Key9 = 57 -- 9 +KeyColon = 58 -- : +KeySemicolon = 59 -- ; +KeyLess = 60 -- < +KeyEqual = 61 -- = +KeyGreater = 62 -- > +KeyQuestion = 63 -- ? +KeyAtSign = 64 -- @ +KeyA = 65 -- a +KeyB = 66 -- b +KeyC = 67 -- c +KeyD = 68 -- d +KeyE = 69 -- e +KeyF = 70 -- f +KeyG = 71 -- g +KeyH = 72 -- h +KeyI = 73 -- i +KeyJ = 74 -- j +KeyK = 75 -- k +KeyL = 76 -- l +KeyM = 77 -- m +KeyN = 78 -- n +KeyO = 79 -- o +KeyP = 80 -- p +KeyQ = 81 -- q +KeyR = 82 -- r +KeyS = 83 -- s +KeyT = 84 -- t +KeyU = 85 -- u +KeyV = 86 -- v +KeyW = 87 -- w +KeyX = 88 -- x +KeyY = 89 -- y +KeyZ = 90 -- z +KeyLeftBracket = 91 -- [ +KeyBackslash = 92 -- '\' +KeyRightBracket = 93 -- ] +KeyCaret = 94 -- ^ +KeyUnderscore = 95 -- _ +KeyGrave = 96 -- ` +KeyLeftCurly = 123 -- { +KeyBar = 124 -- | +KeyRightCurly = 125 -- } +KeyTilde = 126 -- ~ +KeyF1 = 128 +KeyF2 = 129 +KeyF3 = 130 +KeyF4 = 131 +KeyF5 = 132 +KeyF6 = 134 +KeyF7 = 135 +KeyF8 = 136 +KeyF9 = 137 +KeyF10 = 138 +KeyF11 = 139 +KeyF12 = 140 +KeyNumpad0 = 141 +KeyNumpad1 = 142 +KeyNumpad2 = 143 +KeyNumpad3 = 144 +KeyNumpad4 = 145 +KeyNumpad5 = 146 +KeyNumpad6 = 147 +KeyNumpad7 = 148 +KeyNumpad8 = 149 +KeyNumpad9 = 150 + +FirstKey = KeyUnknown +LastKey = KeyNumpad9 + +ExtendedActivate = 0 +ExtendedLocales = 1 +ExtendedParticles = 2 + +-- @} + +KeyCodeDescs = { + [KeyUnknown] = 'Unknown', + [KeyEscape] = 'Escape', + [KeyTab] = 'Tab', + [KeyBackspace] = 'Backspace', + [KeyEnter] = 'Enter', + [KeyInsert] = 'Insert', + [KeyDelete] = 'Delete', + [KeyPause] = 'Pause', + [KeyPrintScreen] = 'PrintScreen', + [KeyHome] = 'Home', + [KeyEnd] = 'End', + [KeyPageUp] = 'PageUp', + [KeyPageDown] = 'PageDown', + [KeyUp] = 'Up', + [KeyDown] = 'Down', + [KeyLeft] = 'Left', + [KeyRight] = 'Right', + [KeyNumLock] = 'NumLock', + [KeyScrollLock] = 'ScrollLock', + [KeyCapsLock] = 'CapsLock', + [KeyCtrl] = 'Ctrl', + [KeyShift] = 'Shift', + [KeyAlt] = 'Alt', + [KeyMeta] = 'Meta', + [KeyMenu] = 'Menu', + [KeySpace] = 'Space', + [KeyExclamation] = '!', + [KeyQuote] = '\"', + [KeyNumberSign] = '#', + [KeyDollar] = '$', + [KeyPercent] = '%', + [KeyAmpersand] = '&', + [KeyApostrophe] = '\'', + [KeyLeftParen] = '(', + [KeyRightParen] = ')', + [KeyAsterisk] = '*', + [KeyPlus] = 'Plus', + [KeyComma] = ',', + [KeyMinus] = '-', + [KeyPeriod] = '.', + [KeySlash] = '/', + [Key0] = '0', + [Key1] = '1', + [Key2] = '2', + [Key3] = '3', + [Key4] = '4', + [Key5] = '5', + [Key6] = '6', + [Key7] = '7', + [Key8] = '8', + [Key9] = '9', + [KeyColon] = ':', + [KeySemicolon] = ';', + [KeyLess] = '<', + [KeyEqual] = '=', + [KeyGreater] = '>', + [KeyQuestion] = '?', + [KeyAtSign] = '@', + [KeyA] = 'A', + [KeyB] = 'B', + [KeyC] = 'C', + [KeyD] = 'D', + [KeyE] = 'E', + [KeyF] = 'F', + [KeyG] = 'G', + [KeyH] = 'H', + [KeyI] = 'I', + [KeyJ] = 'J', + [KeyK] = 'K', + [KeyL] = 'L', + [KeyM] = 'M', + [KeyN] = 'N', + [KeyO] = 'O', + [KeyP] = 'P', + [KeyQ] = 'Q', + [KeyR] = 'R', + [KeyS] = 'S', + [KeyT] = 'T', + [KeyU] = 'U', + [KeyV] = 'V', + [KeyW] = 'W', + [KeyX] = 'X', + [KeyY] = 'Y', + [KeyZ] = 'Z', + [KeyLeftBracket] = '[', + [KeyBackslash] = '\\', + [KeyRightBracket] = ']', + [KeyCaret] = '^', + [KeyUnderscore] = '_', + [KeyGrave] = '`', + [KeyLeftCurly] = '{', + [KeyBar] = '|', + [KeyRightCurly] = '}', + [KeyTilde] = '~', + [KeyF1] = 'F1', + [KeyF2] = 'F2', + [KeyF3] = 'F3', + [KeyF4] = 'F4', + [KeyF5] = 'F5', + [KeyF6] = 'F6', + [KeyF7] = 'F7', + [KeyF8] = 'F8', + [KeyF9] = 'F9', + [KeyF10] = 'F10', + [KeyF11] = 'F11', + [KeyF12] = 'F12', + [KeyNumpad0] = 'Numpad0', + [KeyNumpad1] = 'Numpad1', + [KeyNumpad2] = 'Numpad2', + [KeyNumpad3] = 'Numpad3', + [KeyNumpad4] = 'Numpad4', + [KeyNumpad5] = 'Numpad5', + [KeyNumpad6] = 'Numpad6', + [KeyNumpad7] = 'Numpad7', + [KeyNumpad8] = 'Numpad8', + [KeyNumpad9] = 'Numpad9', +} + +NetworkMessageTypes = { + Boolean = 1, + U8 = 2, + U16 = 3, + U32 = 4, + U64 = 5, + NumberString = 6, + String = 7, + Table = 8, +} + +SoundChannels = { + Music = 1, + Ambient = 2, + Effect = 3, + Bot = 4 +} diff --git a/800OTClient/modules/corelib/corelib.otmod b/800OTClient/modules/corelib/corelib.otmod new file mode 100644 index 0000000..75cce3b --- /dev/null +++ b/800OTClient/modules/corelib/corelib.otmod @@ -0,0 +1,34 @@ +Module + name: corelib + description: Contains core lua classes, functions and constants used by other modules + author: OTClient team + website: https://github.com/edubart/otclient + reloadable: false + + @onLoad: | + dofile 'math' + dofile 'string' + dofile 'table' + dofile 'bitwise' + dofile 'struct' + + dofile 'const' + dofile 'util' + dofile 'globals' + dofile 'config' + dofile 'settings' + dofile 'keyboard' + dofile 'mouse' + dofile 'net' + + dofiles 'classes' + dofiles 'ui' + + dofile 'inputmessage' + dofile 'outputmessage' + dofile 'orderedtable' + + dofile 'base64' + dofile 'json' + dofile 'http' + dofile 'test' diff --git a/800OTClient/modules/corelib/globals.lua b/800OTClient/modules/corelib/globals.lua new file mode 100644 index 0000000..172f2c6 --- /dev/null +++ b/800OTClient/modules/corelib/globals.lua @@ -0,0 +1,76 @@ +-- @docvars @{ + +-- root widget +rootWidget = g_ui.getRootWidget() +modules = package.loaded + +-- G is used as a global table to save variables in memory between reloads +G = G or {} + +-- @} + +-- @docfuncs @{ + +function scheduleEvent(callback, delay) + local desc = "lua" + local info = debug.getinfo(2, "Sl") + if info then + desc = info.short_src .. ":" .. info.currentline + end + local event = g_dispatcher.scheduleEvent(desc, callback, delay) + -- must hold a reference to the callback, otherwise it would be collected + event._callback = callback + return event +end + +function addEvent(callback, front) + local desc = "lua" + local info = debug.getinfo(2, "Sl") + if info then + desc = info.short_src .. ":" .. info.currentline + end + local event = g_dispatcher.addEvent(desc, callback, front) + -- must hold a reference to the callback, otherwise it would be collected + event._callback = callback + return event +end + +function cycleEvent(callback, interval) + local desc = "lua" + local info = debug.getinfo(2, "Sl") + if info then + desc = info.short_src .. ":" .. info.currentline + end + local event = g_dispatcher.cycleEvent(desc, callback, interval) + -- must hold a reference to the callback, otherwise it would be collected + event._callback = callback + return event +end + +function periodicalEvent(eventFunc, conditionFunc, delay, autoRepeatDelay) + delay = delay or 30 + autoRepeatDelay = autoRepeatDelay or delay + + local func + func = function() + if conditionFunc and not conditionFunc() then + func = nil + return + end + eventFunc() + scheduleEvent(func, delay) + end + + scheduleEvent(function() + func() + end, autoRepeatDelay) +end + +function removeEvent(event) + if event then + event:cancel() + event._callback = nil + end +end + +-- @} \ No newline at end of file diff --git a/800OTClient/modules/corelib/http.lua b/800OTClient/modules/corelib/http.lua new file mode 100644 index 0000000..1ff47b0 --- /dev/null +++ b/800OTClient/modules/corelib/http.lua @@ -0,0 +1,287 @@ +HTTP = { + timeout=5, + websocketTimeout=15, + agent="Mozilla/5.0", + imageId=1000, + images={}, + operations={}, +} + +function HTTP.get(url, callback) + if not g_http or not g_http.get then + return error("HTTP.get is not supported") + end + local operation = g_http.get(url, HTTP.timeout) + HTTP.operations[operation] = {type="get", url=url, callback=callback} + return operation +end + +function HTTP.getJSON(url, callback) + if not g_http or not g_http.get then + return error("HTTP.getJSON is not supported") + end + local operation = g_http.get(url, HTTP.timeout) + HTTP.operations[operation] = {type="get", json=true, url=url, callback=callback} + return operation +end + +function HTTP.post(url, data, callback) + if not g_http or not g_http.post then + return error("HTTP.post is not supported") + end + if type(data) == "table" then + data = json.encode(data) + end + local operation = g_http.post(url, data, HTTP.timeout) + HTTP.operations[operation] = {type="post", url=url, callback=callback} + return operation +end + +function HTTP.postJSON(url, data, callback) + if not g_http or not g_http.post then + return error("HTTP.postJSON is not supported") + end + if type(data) == "table" then + data = json.encode(data) + end + local operation = g_http.post(url, data, HTTP.timeout, true) + HTTP.operations[operation] = {type="post", json=true, url=url, callback=callback} + return operation +end + +function HTTP.download(url, file, callback, progressCallback) + if not g_http or not g_http.download then + return error("HTTP.download is not supported") + end + local operation = g_http.download(url, file, HTTP.timeout) + HTTP.operations[operation] = {type="download", url=url, file=file, callback=callback, progressCallback=progressCallback} + return operation +end + +function HTTP.downloadImage(url, callback) + if not g_http or not g_http.download then + return error("HTTP.downloadImage is not supported") + end + if HTTP.images[url] ~= nil then + if callback then + callback('/downloads/' .. HTTP.images[url], nil) + end + return + end + local file = "autoimage_" .. HTTP.imageId .. ".png" + HTTP.imageId = HTTP.imageId + 1 + local operation = g_http.download(url, file, HTTP.timeout) + HTTP.operations[operation] = {type="image", url=url, file=file, callback=callback} + return operation +end + +function HTTP.webSocket(url, callbacks, timeout, jsonWebsocket) + if not g_http or not g_http.ws then + return error("WebSocket is not supported") + end + if not timeout or timeout < 1 then + timeout = HTTP.websocketTimeout + end + local operation = g_http.ws(url, timeout) + HTTP.operations[operation] = {type="ws", json=jsonWebsocket, url=url, callbacks=callbacks} + return { + id = operation, + url = url, + close = function() + g_http.wsClose(operation) + end, + send = function(message) + if type(message) == "table" then + message = json.encode(message) + end + g_http.wsSend(operation, message) + end + } +end +HTTP.WebSocket = HTTP.webSocket + +function HTTP.webSocketJSON(url, callbacks, timeout) + return HTTP.webSocket(url, callbacks, timeout, true) +end +HTTP.WebSocketJSON = HTTP.webSocketJSON + +function HTTP.cancel(operationId) + if not g_http or not g_http.cancel then + return + end + HTTP.operations[operationId] = nil + return g_http.cancel(operationId) +end + +function HTTP.onGet(operationId, url, err, data) + local operation = HTTP.operations[operationId] + if operation == nil then + return + end + if err and err:len() == 0 then + err = nil + end + if not err and operation.json then + if data:len() == 0 then + data = "null" + end + local status, result = pcall(function() return json.decode(data) end) + if not status then + err = "JSON ERROR: " .. result + if data and data:len() > 0 then + err = err .. " (" .. data:sub(1, 100) .. ")" + end + end + data = result + end + if operation.callback then + operation.callback(data, err) + end +end + +function HTTP.onGetProgress(operationId, url, progress) + local operation = HTTP.operations[operationId] + if operation == nil then + return + end + +end + +function HTTP.onPost(operationId, url, err, data) + local operation = HTTP.operations[operationId] + if operation == nil then + return + end + if err and err:len() == 0 then + err = nil + end + if not err and operation.json then + if data:len() == 0 then + data = "null" + end + local status, result = pcall(function() return json.decode(data) end) + if not status then + err = "JSON ERROR: " .. result + if data and data:len() > 0 then + err = err .. " (" .. data:sub(1, 100) .. ")" + end + end + data = result + end + if operation.callback then + operation.callback(data, err) + end +end + +function HTTP.onPostProgress(operationId, url, progress) + local operation = HTTP.operations[operationId] + if operation == nil then + return + end +end + +function HTTP.onDownload(operationId, url, err, path, checksum) + local operation = HTTP.operations[operationId] + if operation == nil then + return + end + if err and err:len() == 0 then + err = nil + end + if operation.callback then + if operation["type"] == "image" then + if not err then + HTTP.images[url] = path + end + operation.callback('/downloads/' .. path, err) + else + operation.callback(path, checksum, err) + end + end +end + +function HTTP.onDownloadProgress(operationId, url, progress, speed) + local operation = HTTP.operations[operationId] + if operation == nil then + return + end + if operation.progressCallback then + operation.progressCallback(progress, speed) + end +end + +function HTTP.onWsOpen(operationId, message) + local operation = HTTP.operations[operationId] + if operation == nil then + return + end + if operation.callbacks.onOpen then + operation.callbacks.onOpen(message, operationId) + end +end + +function HTTP.onWsMessage(operationId, message) + local operation = HTTP.operations[operationId] + if operation == nil then + return + end + if operation.callbacks.onMessage then + if operation.json then + if message:len() == 0 then + message = "null" + end + local status, result = pcall(function() return json.decode(message) end) + local err = nil + if not status then + err = "JSON ERROR: " .. result + if message and message:len() > 0 then + err = err .. " (" .. message:sub(1, 100) .. ")" + end + end + if err then + if operation.callbacks.onError then + operation.callbacks.onError(err, operationId) + end + else + operation.callbacks.onMessage(result, operationId) + end + else + operation.callbacks.onMessage(message, operationId) + end + end +end + +function HTTP.onWsClose(operationId, message) + local operation = HTTP.operations[operationId] + if operation == nil then + return + end + if operation.callbacks.onClose then + operation.callbacks.onClose(message, operationId) + end +end + +function HTTP.onWsError(operationId, message) + local operation = HTTP.operations[operationId] + if operation == nil then + return + end + if operation.callbacks.onError then + operation.callbacks.onError(message, operationId) + end +end + +connect(g_http, + { + onGet = HTTP.onGet, + onGetProgress = HTTP.onGetProgress, + onPost = HTTP.onPost, + onPostProgress = HTTP.onPostProgress, + onDownload = HTTP.onDownload, + onDownloadProgress = HTTP.onDownloadProgress, + onWsOpen = HTTP.onWsOpen, + onWsMessage = HTTP.onWsMessage, + onWsClose = HTTP.onWsClose, + onWsError = HTTP.onWsError, + }) +g_http.setUserAgent(HTTP.agent) diff --git a/800OTClient/modules/corelib/inputmessage.lua b/800OTClient/modules/corelib/inputmessage.lua new file mode 100644 index 0000000..48597e4 --- /dev/null +++ b/800OTClient/modules/corelib/inputmessage.lua @@ -0,0 +1,51 @@ +function InputMessage:getData() + local dataType = self:getU8() + if dataType == NetworkMessageTypes.Boolean then + return numbertoboolean(self:getU8()) + elseif dataType == NetworkMessageTypes.U8 then + return self:getU8() + elseif dataType == NetworkMessageTypes.U16 then + return self:getU16() + elseif dataType == NetworkMessageTypes.U32 then + return self:getU32() + elseif dataType == NetworkMessageTypes.U64 then + return self:getU64() + elseif dataType == NetworkMessageTypes.NumberString then + return tonumber(self:getString()) + elseif dataType == NetworkMessageTypes.String then + return self:getString() + elseif dataType == NetworkMessageTypes.Table then + return self:getTable() + else + perror('Unknown data type ' .. dataType) + end + return nil +end + +function InputMessage:getTable() + local ret = {} + local size = self:getU16() + for i=1,size do + local index = self:getData() + local value = self:getData() + ret[index] = value + end + return ret +end + +function InputMessage:getColor() + local color = {} + color.r = self:getU8() + color.g = self:getU8() + color.b = self:getU8() + color.a = self:getU8() + return color +end + +function InputMessage:getPosition() + local position = {} + position.x = self:getU16() + position.y = self:getU16() + position.z = self:getU8() + return position +end diff --git a/800OTClient/modules/corelib/json.lua b/800OTClient/modules/corelib/json.lua new file mode 100644 index 0000000..4535c6f --- /dev/null +++ b/800OTClient/modules/corelib/json.lua @@ -0,0 +1,419 @@ +-- +-- json.lua +-- +-- Copyright (c) 2019 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +json = { _version = "0.1.1" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\\\", + [ "\"" ] = "\\\"", + [ "\b" ] = "\\b", + [ "\f" ] = "\\f", + [ "\n" ] = "\\n", + [ "\r" ] = "\\r", + [ "\t" ] = "\\t", +} + +local escape_char_map_inv = { [ "\\/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function make_indent(state) + return string.rep(" ", state.currentIndentLevel * state.indent) +end + + +local function escape_char(c) + return escape_char_map[c] or string.format("\\u%04x", c:byte()) +end + + +local function encode_nil() + return "null" +end + + +local function encode_table(val, state) + local res = {} + local stack = state.stack + local pretty = state.indent > 0 + + local close_indent = make_indent(state) + local comma = pretty and ",\n" or "," + local colon = pretty and ": " or ":" + local open_brace = pretty and "{\n" or "{" + local close_brace = pretty and ("\n" .. close_indent .. "}") or "}" + local open_bracket = pretty and "[\n" or "[" + local close_bracket = pretty and ("\n" .. close_indent .. "]") or "]" + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for _, v in ipairs(val) do + state.currentIndentLevel = state.currentIndentLevel + 1 + table.insert(res, make_indent(state) .. encode(v, state)) + state.currentIndentLevel = state.currentIndentLevel - 1 + end + stack[val] = nil + return open_bracket .. table.concat(res, comma) .. close_bracket + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + state.currentIndentLevel = state.currentIndentLevel + 1 + table.insert(res, make_indent(state) .. encode(k, state) .. colon .. encode(v, state)) + state.currentIndentLevel = state.currentIndentLevel - 1 + end + stack[val] = nil + return open_brace .. table.concat(res, comma) .. close_brace + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, state) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, state) + end + error("unexpected type '" .. t .. "'") +end + +function json.encode(val, indent) + local state = { + indent = indent or 0, + currentIndentLevel = 0, + stack = {} + } + return encode(val, state) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(3, 6), 16 ) + local n2 = tonumber( s:sub(9, 12), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local has_unicode_escape = false + local has_surrogate_escape = false + local has_escape = false + local last + for j = i + 1, #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + end + + if last == 92 then -- "\\" (escape char) + if x == 117 then -- "u" (unicode escape sequence) + local hex = str:sub(j + 1, j + 5) + if not hex:find("%x%x%x%x") then + decode_error(str, j, "invalid unicode escape in string") + end + if hex:find("^[dD][89aAbB]") then + has_surrogate_escape = true + else + has_unicode_escape = true + end + else + local c = string.char(x) + if not escape_chars[c] then + decode_error(str, j, "invalid escape char '" .. c .. "' in string") + end + has_escape = true + end + last = nil + + elseif x == 34 then -- '"' (end of string) + local s = str:sub(i + 1, j - 1) + if has_surrogate_escape then + s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape) + end + if has_unicode_escape then + s = s:gsub("\\u....", parse_unicode_escape) + end + if has_escape then + s = s:gsub("\\.", escape_char_map_inv) + end + return s, j + 1 + + else + last = x + end + end + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end diff --git a/800OTClient/modules/corelib/keyboard.lua b/800OTClient/modules/corelib/keyboard.lua new file mode 100644 index 0000000..463fe1e --- /dev/null +++ b/800OTClient/modules/corelib/keyboard.lua @@ -0,0 +1,251 @@ +-- @docclass +g_keyboard = {} + +-- private functions +function translateKeyCombo(keyCombo) + if not keyCombo or #keyCombo == 0 then return nil end + local keyComboDesc = '' + for k,v in pairs(keyCombo) do + local keyDesc = KeyCodeDescs[v] + if keyDesc == nil then return nil end + keyComboDesc = keyComboDesc .. '+' .. keyDesc + end + keyComboDesc = keyComboDesc:sub(2) + return keyComboDesc +end + +local function getKeyCode(key) + for keyCode, keyDesc in pairs(KeyCodeDescs) do + if keyDesc:lower() == key:trim():lower() then + return keyCode + end + end +end + +function retranslateKeyComboDesc(keyComboDesc) + if keyComboDesc == nil then + error('Unable to translate key combo \'' .. keyComboDesc .. '\'') + end + + if type(keyComboDesc) == 'number' then + keyComboDesc = tostring(keyComboDesc) + end + + local keyCombo = {} + for i,currentKeyDesc in ipairs(keyComboDesc:split('+')) do + for keyCode, keyDesc in pairs(KeyCodeDescs) do + if keyDesc:lower() == currentKeyDesc:trim():lower() then + table.insert(keyCombo, keyCode) + end + end + end + return translateKeyCombo(keyCombo) +end + +function determineKeyComboDesc(keyCode, keyboardModifiers) + local keyCombo = {} + if keyCode == KeyCtrl or keyCode == KeyShift or keyCode == KeyAlt then + table.insert(keyCombo, keyCode) + elseif KeyCodeDescs[keyCode] ~= nil then + if keyboardModifiers == KeyboardCtrlModifier then + table.insert(keyCombo, KeyCtrl) + elseif keyboardModifiers == KeyboardAltModifier then + table.insert(keyCombo, KeyAlt) + elseif keyboardModifiers == KeyboardCtrlAltModifier then + table.insert(keyCombo, KeyCtrl) + table.insert(keyCombo, KeyAlt) + elseif keyboardModifiers == KeyboardShiftModifier then + table.insert(keyCombo, KeyShift) + elseif keyboardModifiers == KeyboardCtrlShiftModifier then + table.insert(keyCombo, KeyCtrl) + table.insert(keyCombo, KeyShift) + elseif keyboardModifiers == KeyboardAltShiftModifier then + table.insert(keyCombo, KeyAlt) + table.insert(keyCombo, KeyShift) + elseif keyboardModifiers == KeyboardCtrlAltShiftModifier then + table.insert(keyCombo, KeyCtrl) + table.insert(keyCombo, KeyAlt) + table.insert(keyCombo, KeyShift) + end + table.insert(keyCombo, keyCode) + end + return translateKeyCombo(keyCombo) +end + +local function onWidgetKeyDown(widget, keyCode, keyboardModifiers) + if keyCode == KeyUnknown then return false end + local callback = widget.boundAloneKeyDownCombos[determineKeyComboDesc(keyCode, KeyboardNoModifier)] + signalcall(callback, widget, keyCode) + callback = widget.boundKeyDownCombos[determineKeyComboDesc(keyCode, keyboardModifiers)] + return signalcall(callback, widget, keyCode) +end + +local function onWidgetKeyUp(widget, keyCode, keyboardModifiers) + if keyCode == KeyUnknown then return false end + local callback = widget.boundAloneKeyUpCombos[determineKeyComboDesc(keyCode, KeyboardNoModifier)] + signalcall(callback, widget, keyCode) + callback = widget.boundKeyUpCombos[determineKeyComboDesc(keyCode, keyboardModifiers)] + return signalcall(callback, widget, keyCode) +end + +local function onWidgetKeyPress(widget, keyCode, keyboardModifiers, autoRepeatTicks) + if keyCode == KeyUnknown then return false end + local callback = widget.boundKeyPressCombos[determineKeyComboDesc(keyCode, keyboardModifiers)] + return signalcall(callback, widget, keyCode, autoRepeatTicks) +end + +local function connectKeyDownEvent(widget) + if widget.boundKeyDownCombos then return end + connect(widget, { onKeyDown = onWidgetKeyDown }) + widget.boundKeyDownCombos = {} + widget.boundAloneKeyDownCombos = {} +end + +local function connectKeyUpEvent(widget) + if widget.boundKeyUpCombos then return end + connect(widget, { onKeyUp = onWidgetKeyUp }) + widget.boundKeyUpCombos = {} + widget.boundAloneKeyUpCombos = {} +end + +local function connectKeyPressEvent(widget) + if widget.boundKeyPressCombos then return end + connect(widget, { onKeyPress = onWidgetKeyPress }) + widget.boundKeyPressCombos = {} +end + +-- public functions +function g_keyboard.bindKeyDown(keyComboDesc, callback, widget, alone) + widget = widget or rootWidget + connectKeyDownEvent(widget) + local keyComboDesc = retranslateKeyComboDesc(keyComboDesc) + if alone then + connect(widget.boundAloneKeyDownCombos, keyComboDesc, callback) + else + connect(widget.boundKeyDownCombos, keyComboDesc, callback) + end +end + +function g_keyboard.bindKeyUp(keyComboDesc, callback, widget, alone) + widget = widget or rootWidget + connectKeyUpEvent(widget) + local keyComboDesc = retranslateKeyComboDesc(keyComboDesc) + if alone then + connect(widget.boundAloneKeyUpCombos, keyComboDesc, callback) + else + connect(widget.boundKeyUpCombos, keyComboDesc, callback) + end +end + +function g_keyboard.bindKeyPress(keyComboDesc, callback, widget) + widget = widget or rootWidget + connectKeyPressEvent(widget) + local keyComboDesc = retranslateKeyComboDesc(keyComboDesc) + connect(widget.boundKeyPressCombos, keyComboDesc, callback) +end + +local function getUnbindArgs(arg1, arg2) + local callback + local widget + if type(arg1) == 'function' then callback = arg1 + elseif type(arg2) == 'function' then callback = arg2 end + if type(arg1) == 'userdata' then widget = arg1 + elseif type(arg2) == 'userdata' then widget = arg2 end + widget = widget or rootWidget + return callback, widget +end + +function g_keyboard.unbindKeyDown(keyComboDesc, arg1, arg2) + local callback, widget = getUnbindArgs(arg1, arg2) + if widget.boundKeyDownCombos == nil then return end + local keyComboDesc = retranslateKeyComboDesc(keyComboDesc) + disconnect(widget.boundKeyDownCombos, keyComboDesc, callback) +end + +function g_keyboard.unbindKeyUp(keyComboDesc, arg1, arg2) + local callback, widget = getUnbindArgs(arg1, arg2) + if widget.boundKeyUpCombos == nil then return end + local keyComboDesc = retranslateKeyComboDesc(keyComboDesc) + disconnect(widget.boundKeyUpCombos, keyComboDesc, callback) +end + +function g_keyboard.unbindKeyPress(keyComboDesc, arg1, arg2) + local callback, widget = getUnbindArgs(arg1, arg2) + if widget.boundKeyPressCombos == nil then return end + local keyComboDesc = retranslateKeyComboDesc(keyComboDesc) + disconnect(widget.boundKeyPressCombos, keyComboDesc, callback) +end + +function g_keyboard.getModifiers() + return g_window.getKeyboardModifiers() +end + +function g_keyboard.isKeyPressed(key) + if type(key) == 'string' then + key = getKeyCode(key) + end + return g_window.isKeyPressed(key) +end + +function g_keyboard.areKeysPressed(keyComboDesc) + for i,currentKeyDesc in ipairs(keyComboDesc:split('+')) do + for keyCode, keyDesc in pairs(KeyCodeDescs) do + if keyDesc:lower() == currentKeyDesc:trim():lower() then + if keyDesc:lower() == "ctrl" then + if not g_keyboard.isCtrlPressed() then + return false + end + elseif keyDesc:lower() == "shift" then + if not g_keyboard.isShiftPressed() then + return false + end + elseif keyDesc:lower() == "alt" then + if not g_keyboard.isAltPressed() then + return false + end + elseif not g_window.isKeyPressed(keyCode) then + return false + end + end + end + end + return true +end + +function g_keyboard.isKeySetPressed(keys, all) + all = all or false + local result = {} + for k,v in pairs(keys) do + if type(v) == 'string' then + v = getKeyCode(v) + end + if g_window.isKeyPressed(v) then + if not all then + return true + end + table.insert(result, true) + end + end + return #result == #keys +end + +function g_keyboard.isInUse() + for i = FirstKey, LastKey do + if g_window.isKeyPressed(key) then + return true + end + end + return false +end + +function g_keyboard.isCtrlPressed() + return bit32.band(g_window.getKeyboardModifiers(), KeyboardCtrlModifier) ~= 0 +end + +function g_keyboard.isAltPressed() + return bit32.band(g_window.getKeyboardModifiers(), KeyboardAltModifier) ~= 0 +end + +function g_keyboard.isShiftPressed() + return bit32.band(g_window.getKeyboardModifiers(), KeyboardShiftModifier) ~= 0 +end diff --git a/800OTClient/modules/corelib/math.lua b/800OTClient/modules/corelib/math.lua new file mode 100644 index 0000000..79c6670 --- /dev/null +++ b/800OTClient/modules/corelib/math.lua @@ -0,0 +1,35 @@ +-- @docclass math + +local U8 = 2^8 +local U16 = 2^16 +local U32 = 2^32 +local U64 = 2^64 + +function math.round(num, idp) + local mult = 10^(idp or 0) + if num >= 0 then + return math.floor(num * mult + 0.5) / mult + else + return math.ceil(num * mult - 0.5) / mult + end +end + +function math.isu8(num) + return math.isinteger(num) and num >= 0 and num < U8 +end + +function math.isu16(num) + return math.isinteger(num) and num >= U8 and num < U16 +end + +function math.isu32(num) + return math.isinteger(num) and num >= U16 and num < U32 +end + +function math.isu64(num) + return math.isinteger(num) and num >= U32 and num < U64 +end + +function math.isinteger(num) + return ((type(num) == 'number') and (num == math.floor(num))) +end diff --git a/800OTClient/modules/corelib/mouse.lua b/800OTClient/modules/corelib/mouse.lua new file mode 100644 index 0000000..d46314e --- /dev/null +++ b/800OTClient/modules/corelib/mouse.lua @@ -0,0 +1,36 @@ +-- @docclass +function g_mouse.bindAutoPress(widget, callback, delay, button) + local button = button or MouseLeftButton + connect(widget, { onMousePress = function(widget, mousePos, mouseButton) + if mouseButton ~= button then + return false + end + local startTime = g_clock.millis() + callback(widget, mousePos, mouseButton, 0) + periodicalEvent(function() + callback(widget, g_window.getMousePosition(), mouseButton, g_clock.millis() - startTime) + end, function() + return g_mouse.isPressed(mouseButton) + end, 30, delay) + return true + end }) +end + +function g_mouse.bindPressMove(widget, callback) + connect(widget, { onMouseMove = function(widget, mousePos, mouseMoved) + if widget:isPressed() then + callback(mousePos, mouseMoved) + return true + end + end }) +end + +function g_mouse.bindPress(widget, callback, button) + connect(widget, { onMousePress = function(widget, mousePos, mouseButton) + if not button or button == mouseButton then + callback(mousePos, mouseButton) + return true + end + return false + end }) +end diff --git a/800OTClient/modules/corelib/net.lua b/800OTClient/modules/corelib/net.lua new file mode 100644 index 0000000..b2d5994 --- /dev/null +++ b/800OTClient/modules/corelib/net.lua @@ -0,0 +1,16 @@ +function translateNetworkError(errcode, connecting, errdesc) + local text + if errcode == 111 then + text = tr('Connection refused, the server might be offline or restarting.\nPlease try again later.') + elseif errcode == 110 then + text = tr('Connection timed out. Either your network is failing or the server is offline.') + elseif errcode == 1 then + text = tr('Connection failed, the server address does not exist.') + elseif connecting then + text = tr('Connection failed.') + else + text = tr('Your connection has been lost.\nEither your network or the server went down.') + end + text = text .. ' ' .. tr('(ERROR %d)', errcode) + return text +end diff --git a/800OTClient/modules/corelib/orderedtable.lua b/800OTClient/modules/corelib/orderedtable.lua new file mode 100644 index 0000000..cf9c839 --- /dev/null +++ b/800OTClient/modules/corelib/orderedtable.lua @@ -0,0 +1,43 @@ +function __genOrderedIndex( t ) + local orderedIndex = {} + for key in pairs(t) do + table.insert( orderedIndex, key ) + end + table.sort( orderedIndex ) + return orderedIndex +end + +function orderedNext(t, state) + -- Equivalent of the next function, but returns the keys in the alphabetic + -- order. We use a temporary ordered key table that is stored in the + -- table being iterated. + + local key = nil + --print("orderedNext: state = "..tostring(state) ) + if state == nil then + -- the first time, generate the index + t.__orderedIndex = __genOrderedIndex( t ) + key = t.__orderedIndex[1] + else + -- fetch the next value + for i = 1,table.getn(t.__orderedIndex) do + if t.__orderedIndex[i] == state then + key = t.__orderedIndex[i+1] + end + end + end + + if key then + return key, t[key] + end + + -- no more value to return, cleanup + t.__orderedIndex = nil + return +end + +function orderedPairs(t) + -- Equivalent of the pairs() function on tables. Allows to iterate + -- in order + return orderedNext, t, nil +end \ No newline at end of file diff --git a/800OTClient/modules/corelib/outputmessage.lua b/800OTClient/modules/corelib/outputmessage.lua new file mode 100644 index 0000000..1e6737e --- /dev/null +++ b/800OTClient/modules/corelib/outputmessage.lua @@ -0,0 +1,69 @@ +function OutputMessage:addData(data) + if type(data) == 'boolean' then + self:addU8(NetworkMessageTypes.Boolean) + self:addU8(booleantonumber(data)) + elseif type(data) == 'number' then + if math.isu8(data) then + self:addU8(NetworkMessageTypes.U8) + self:addU8(data) + elseif math.isu16(data) then + self:addU8(NetworkMessageTypes.U16) + self:addU16(data) + elseif math.isu32(data) then + self:addU8(NetworkMessageTypes.U32) + self:addU32(data) + elseif math.isu64(data) then + self:addU8(NetworkMessageTypes.U64) + self:addU64(data) + else -- negative or non integer numbers + self:addU8(NetworkMessageTypes.NumberString) + self:addString(tostring(data)) + end + elseif type(data) == 'string' then + self:addU8(NetworkMessageTypes.String) + self:addString(data) + elseif type(data) == 'table' then + self:addU8(NetworkMessageTypes.Table) + self:addTable(data) + else + perror('Invalid data type ' .. type(data)) + end +end + +function OutputMessage:addTable(data) + local size = 0 + + -- reserve for size (should be addData, find a way to use it further) + local sizePos = self:getWritePos() + self:addU16(size) + local sizeSize = self:getWritePos() - sizePos + + -- add values + for key,value in pairs(data) do + self:addData(key) + self:addData(value) + size = size + 1 + end + + -- write size + local currentPos = self:getWritePos() + self:setWritePos(sizePos) + self:addU16(size) + + -- fix msg size and go back to end + self:setMessageSize(self:getMessageSize() - sizeSize) + self:setWritePos(currentPos) +end + +function OutputMessage:addColor(color) + self:addU8(color.r) + self:addU8(color.g) + self:addU8(color.b) + self:addU8(color.a) +end + +function OutputMessage:addPosition(position) + self:addU16(position.x) + self:addU16(position.y) + self:addU8(position.z) +end diff --git a/800OTClient/modules/corelib/settings.lua b/800OTClient/modules/corelib/settings.lua new file mode 100644 index 0000000..0eeaae8 --- /dev/null +++ b/800OTClient/modules/corelib/settings.lua @@ -0,0 +1,3 @@ +g_settings = makesingleton(g_configs.getSettings()) + +-- Reserved for future functionality diff --git a/800OTClient/modules/corelib/string.lua b/800OTClient/modules/corelib/string.lua new file mode 100644 index 0000000..e05c0e2 --- /dev/null +++ b/800OTClient/modules/corelib/string.lua @@ -0,0 +1,59 @@ +-- @docclass string + +function string:split(delim) + local start = 1 + local results = {} + while true do + local pos = string.find(self, delim, start, true) + if not pos then + break + end + table.insert(results, string.sub(self, start, pos-1)) + start = pos + string.len(delim) + end + table.insert(results, string.sub(self, start)) + table.removevalue(results, '') + return results +end + +function string:starts(start) + return string.sub(self, 1, #start) == start +end + +function string:ends(test) + return test =='' or string.sub(self,-string.len(test)) == test +end + +function string:trim() + return string.match(self, '^%s*(.*%S)') or '' +end + +function string:explode(sep, limit) + if type(sep) ~= 'string' or tostring(self):len() == 0 or sep:len() == 0 then + return {} + end + + local i, pos, tmp, t = 0, 1, "", {} + for s, e in function() return string.find(self, sep, pos) end do + tmp = self:sub(pos, s - 1):trim() + table.insert(t, tmp) + pos = e + 1 + + i = i + 1 + if limit ~= nil and i == limit then + break + end + end + + tmp = self:sub(pos):trim() + table.insert(t, tmp) + return t +end + +function string:contains(str, checkCase, start, plain) + if(not checkCase) then + self = self:lower() + str = str:lower() + end + return string.find(self, str, start and start or 1, plain == nil and true or false) +end diff --git a/800OTClient/modules/corelib/struct.lua b/800OTClient/modules/corelib/struct.lua new file mode 100644 index 0000000..2ed134d --- /dev/null +++ b/800OTClient/modules/corelib/struct.lua @@ -0,0 +1,173 @@ +Struct = {} + +function Struct.pack(format, ...) + local stream = {} + local vars = {...} + local endianness = true + + for i = 1, format:len() do + local opt = format:sub(i, i) + + if opt == '>' then + endianness = false + elseif opt:find('[bBhHiIlL]') then + local n = opt:find('[hH]') and 2 or opt:find('[iI]') and 4 or opt:find('[lL]') and 8 or 1 + local val = tonumber(table.remove(vars, 1)) + + if val < 0 then + val = val + 2 ^ (n * 8 - 1) + end + + local bytes = {} + for j = 1, n do + table.insert(bytes, string.char(val % (2 ^ 8))) + val = math.floor(val / (2 ^ 8)) + end + + if not endianness then + table.insert(stream, string.reverse(table.concat(bytes))) + else + table.insert(stream, table.concat(bytes)) + end + elseif opt:find('[fd]') then + local val = tonumber(table.remove(vars, 1)) + local sign = 0 + + if val < 0 then + sign = 1 + val = -val + end + + local mantissa, exponent = math.frexp(val) + if val == 0 then + mantissa = 0 + exponent = 0 + else + mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, (opt == 'd') and 53 or 24) + exponent = exponent + ((opt == 'd') and 1022 or 126) + end + + local bytes = {} + if opt == 'd' then + val = mantissa + for i = 1, 6 do + table.insert(bytes, string.char(math.floor(val) % (2 ^ 8))) + val = math.floor(val / (2 ^ 8)) + end + else + table.insert(bytes, string.char(math.floor(mantissa) % (2 ^ 8))) + val = math.floor(mantissa / (2 ^ 8)) + table.insert(bytes, string.char(math.floor(val) % (2 ^ 8))) + val = math.floor(val / (2 ^ 8)) + end + + table.insert(bytes, string.char(math.floor(exponent * ((opt == 'd') and 16 or 128) + val) % (2 ^ 8))) + val = math.floor((exponent * ((opt == 'd') and 16 or 128) + val) / (2 ^ 8)) + table.insert(bytes, string.char(math.floor(sign * 128 + val) % (2 ^ 8))) + val = math.floor((sign * 128 + val) / (2 ^ 8)) + + if not endianness then + table.insert(stream, string.reverse(table.concat(bytes))) + else + table.insert(stream, table.concat(bytes)) + end + elseif opt == 's' then + table.insert(stream, tostring(table.remove(vars, 1))) + table.insert(stream, string.char(0)) + elseif opt == 'c' then + local n = format:sub(i + 1):match('%d+') + local length = tonumber(n) + + if length > 0 then + local str = tostring(table.remove(vars, 1)) + if length - str:len() > 0 then + str = str .. string.rep(' ', length - str:len()) + end + table.insert(stream, str:sub(1, length)) + end + i = i + n:len() + end + end + + return table.concat(stream) +end + +function Struct.unpack(format, stream) + local vars = {} + local iterator = 1 + local endianness = true + + for i = 1, format:len() do + local opt = format:sub(i, i) + + if opt == '>' then + endianness = false + elseif opt:find('[bBhHiIlL]') then + local n = opt:find('[hH]') and 2 or opt:find('[iI]') and 4 or opt:find('[lL]') and 8 or 1 + local signed = opt:lower() == opt + + local val = 0 + for j = 1, n do + local byte = string.byte(stream:sub(iterator, iterator)) + if endianness then + val = val + byte * (2 ^ ((j - 1) * 8)) + else + val = val + byte * (2 ^ ((n - j) * 8)) + end + iterator = iterator + 1 + end + + if signed then + val = val - 2 ^ (n * 8 - 1) + end + + table.insert(vars, val) + elseif opt:find('[fd]') then + local n = (opt == 'd') and 8 or 4 + local x = stream:sub(iterator, iterator + n - 1) + iterator = iterator + n + + if not endianness then + x = string.reverse(x) + end + + local sign = 1 + local mantissa = string.byte(x, (opt == 'd') and 7 or 3) % ((opt == 'd') and 16 or 128) + for i = n - 2, 1, -1 do + mantissa = mantissa * (2 ^ 8) + string.byte(x, i) + end + + if string.byte(x, n) > 127 then + sign = -1 + end + + local exponent = (string.byte(x, n) % 128) * ((opt == 'd') and 16 or 2) + math.floor(string.byte(x, n - 1) / ((opt == 'd') and 16 or 128)) + if exponent == 0 then + table.insert(vars, 0.0) + else + mantissa = (math.ldexp(mantissa, (opt == 'd') and -52 or -23) + 1) * sign + table.insert(vars, math.ldexp(mantissa, exponent - ((opt == 'd') and 1023 or 127))) + end + elseif opt == 's' then + local bytes = {} + for j = iterator, stream:len() do + if stream:sub(j, j) == string.char(0) then + break + end + + table.insert(bytes, stream:sub(j, j)) + end + + local str = table.concat(bytes) + iterator = iterator + str:len() + 1 + table.insert(vars, str) + elseif opt == 'c' then + local n = format:sub(i + 1):match('%d+') + table.insert(vars, stream:sub(iterator, iterator + tonumber(n))) + iterator = iterator + tonumber(n) + i = i + n:len() + end + end + + return unpack(vars) +end diff --git a/800OTClient/modules/corelib/table.lua b/800OTClient/modules/corelib/table.lua new file mode 100644 index 0000000..60ce55d --- /dev/null +++ b/800OTClient/modules/corelib/table.lua @@ -0,0 +1,287 @@ +-- @docclass table + +function table.dump(t, depth) + if not depth then depth = 0 end + for k,v in pairs(t) do + str = (' '):rep(depth * 2) .. k .. ': ' + if type(v) ~= "table" then + print(str .. tostring(v)) + else + print(str) + table.dump(v, depth+1) + end + end +end + +function table.clear(t) + for k,v in pairs(t) do + t[k] = nil + end +end + +function table.copy(t) + local res = {} + for k,v in pairs(t) do + res[k] = v + end + return res +end + +function table.recursivecopy(t) + local res = {} + for k,v in pairs(t) do + if type(v) == "table" then + res[k] = table.recursivecopy(v) + else + res[k] = v + end + end + return res +end + +function table.selectivecopy(t, keys) + local res = { } + for i,v in ipairs(keys) do + res[v] = t[v] + end + return res +end + +function table.merge(t, src) + for k,v in pairs(src) do + t[k] = v + end +end + +function table.find(t, value, lowercase) + for k,v in pairs(t) do + if lowercase and type(value) == 'string' and type(v) == 'string' then + if v:lower() == value:lower() then return k end + end + if v == value then return k end + end +end + +function table.findbykey(t, key, lowercase) + for k,v in pairs(t) do + if lowercase and type(key) == 'string' and type(k) == 'string' then + if k:lower() == key:lower() then return v end + end + if k == key then return v end + end +end + +function table.contains(t, value, lowercase) + return table.find(t, value, lowercase) ~= nil +end + +function table.findkey(t, key) + if t and type(t) == 'table' then + for k,v in pairs(t) do + if k == key then return k end + end + end +end + +function table.haskey(t, key) + return table.findkey(t, key) ~= nil +end + +function table.removevalue(t, value) + for k,v in pairs(t) do + if v == value then + table.remove(t, k) + return true + end + end + return false +end + +function table.popvalue(value) + local index = nil + for k,v in pairs(t) do + if v == value or not value then + index = k + end + end + if index then + table.remove(t, index) + return true + end + return false +end + +function table.compare(t, other) + if #t ~= #other then return false end + for k,v in pairs(t) do + if v ~= other[k] then return false end + end + return true +end + +function table.empty(t) + if t and type(t) == 'table' then + return next(t) == nil + end + return true +end + +function table.permute(t, n, count) + n = n or #t + for i=1,count or n do + local j = math.random(i, n) + t[i], t[j] = t[j], t[i] + end + return t +end + +function table.findbyfield(t, fieldname, fieldvalue) + for _i,subt in pairs(t) do + if subt[fieldname] == fieldvalue then + return subt + end + end + return nil +end + +function table.size(t) + local size = 0 + for i, n in pairs(t) do + size = size + 1 + end + + return size +end + +function table.tostring(t) + local maxn = #t + local str = "" + for k,v in pairs(t) do + v = tostring(v) + if k == maxn and k ~= 1 then + str = str .. " and " .. v + elseif maxn > 1 and k ~= 1 then + str = str .. ", " .. v + else + str = str .. " " .. v + end + end + return str +end + +function table.collect(t, func) + local res = {} + for k,v in pairs(t) do + local a,b = func(k,v) + if a and b then + res[a] = b + elseif a ~= nil then + table.insert(res,a) + end + end + return res +end + +function table.equals(t, comp) + if type(t) == "table" and type(comp) == "table" then + for k,v in pairs(t) do + if v ~= comp[k] then return false end + end + end + return true +end + +function table.equal(t1,t2,ignore_mt) + local ty1 = type(t1) + local ty2 = type(t2) + if ty1 ~= ty2 then return false end + -- non-table types can be directly compared + if ty1 ~= 'table' and ty2 ~= 'table' then return t1 == t2 end + -- as well as tables which have the metamethod __eq + local mt = getmetatable(t1) + if not ignore_mt and mt and mt.__eq then return t1 == t2 end + for k1,v1 in pairs(t1) do + local v2 = t2[k1] + if v2 == nil or not table.equal(v1,v2) then return false end + end + for k2,v2 in pairs(t2) do + local v1 = t1[k2] + if v1 == nil or not table.equal(v1,v2) then return false end + end + return true +end + +function table.isList(t) + local size = #t + return table.size(t) == size and size > 0 +end + +function table.isStringList(t) + if not table.isList(t) then return false end + for k,v in ipairs(t) do + if type(v) ~= 'string' then + return false + end + end + return true +end + +function table.isStringPairList(t) + if not table.isList(t) then return false end + for k,v in ipairs(t) do + if type(v) ~= 'table' or #v ~= 2 or type(v[1]) ~= 'string' or type(v[2]) ~= 'string' then + return false + end + end + return true +end + +function table.encodeStringPairList(t) + local ret = "" + for k,v in ipairs(t) do + if v[2]:find("\n") then + ret = ret .. v[1] .. ":[[\n" .. v[2] .. "\n]]\n" + else + ret = ret .. v[1] .. ":" .. v[2] .. "\n" + end + end + return ret end + +function table.decodeStringPairList(l) + local ret = {} + local r = regexMatch(l, "(?:^|\\n)([^:^\n]{1,20}):?(.*)(?:$|\\n)") + local multiline = "" + local multilineKey = "" + local multilineActive = false + for k,v in ipairs(r) do + if multilineActive then + local endPos = v[1]:find("%]%]") + if endPos then + if endPos > 1 then + table.insert(ret, {multilineKey, multiline .. "\n" .. v[1]:sub(1, endPos - 1)}) + else + table.insert(ret, {multilineKey, multiline}) + end + multilineActive = false + multiline = "" + multilineKey = "" + else + if multiline:len() == 0 then + multiline = v[1] + else + multiline = multiline .. "\n" .. v[1] + end + end + else + local bracketPos = v[3]:find("%[%[") + if bracketPos == 1 then -- multiline begin + multiline = v[3]:sub(bracketPos + 2) + multilineActive = true + multilineKey = v[2] + elseif v[2]:len() > 0 and v[3]:len() > 0 then + table.insert(ret, {v[2], v[3]}) + end + end + end + return ret +end \ No newline at end of file diff --git a/800OTClient/modules/corelib/test.lua b/800OTClient/modules/corelib/test.lua new file mode 100644 index 0000000..2123e65 --- /dev/null +++ b/800OTClient/modules/corelib/test.lua @@ -0,0 +1,62 @@ +Test = { + tests = {}, + activeTest = 0, + screenShot = 1 +} + +Test.Test = function(name, func) + local testId = #Test.tests + 1 + Test.tests[testId] = { + name = name, + actions = {}, + delay = 0, + start = 0 + } + local test = function(testFunc) + table.insert(Test.tests[testId].actions, {type = "test", value = testFunc}) + end + local wait = function(millis) + Test.tests[testId].delay = Test.tests[testId].delay + millis + table.insert(Test.tests[testId].actions, {type = "wait", value = Test.tests[testId].delay}) + end + local ss = function() + table.insert(Test.tests[testId].actions, {type = "screenshot"}) + end + local fail = function(message) + g_logger.fatal("Test " .. name .. " failed: " .. message) + end + func(test, wait, ss, fail) +end + +Test.run = function() + if Test.activeTest > #Test.tests then + g_logger.info("[TEST] Finished tests. Exiting...") + return g_app.exit() + end + local test = Test.tests[Test.activeTest] + if not test or #test.actions == 0 then + Test.activeTest = Test.activeTest + 1 + local nextTest = Test.tests[Test.activeTest] + if nextTest then + nextTest.start = g_clock.millis() + g_logger.info("[TEST] Starting test: " .. nextTest.name) + end + return scheduleEvent(Test.run, 500) + end + + local action = test.actions[1] + if action.type == "test" then + table.remove(test.actions, 1) + action.value() + elseif action.type == "screenshot" then + table.remove(test.actions, 1) + g_app.doScreenshot(Test.screenShot .. ".png") + Test.screenShot = Test.screenShot + 1 + elseif action.type == "wait" then + if action.value + test.start < g_clock.millis() then + table.remove(test.actions, 1) + end + end + + scheduleEvent(Test.run, 100) +end diff --git a/800OTClient/modules/corelib/ui/effects.lua b/800OTClient/modules/corelib/ui/effects.lua new file mode 100644 index 0000000..16325b5 --- /dev/null +++ b/800OTClient/modules/corelib/ui/effects.lua @@ -0,0 +1,67 @@ +-- @docclass +g_effects = {} + +function g_effects.fadeIn(widget, time, elapsed) + if not elapsed then elapsed = 0 end + if not time then time = 300 end + widget:setOpacity(math.min(elapsed/time, 1)) + removeEvent(widget.fadeEvent) + if elapsed < time then + removeEvent(widget.fadeEvent) + widget.fadeEvent = scheduleEvent(function() + g_effects.fadeIn(widget, time, elapsed + 30) + end, 30) + else + widget.fadeEvent = nil + end +end + +function g_effects.fadeOut(widget, time, elapsed) + if not elapsed then elapsed = 0 end + if not time then time = 300 end + elapsed = math.max((1 - widget:getOpacity()) * time, elapsed) + removeEvent(widget.fadeEvent) + widget:setOpacity(math.max((time - elapsed)/time, 0)) + if elapsed < time then + widget.fadeEvent = scheduleEvent(function() + g_effects.fadeOut(widget, time, elapsed + 30) + end, 30) + else + widget.fadeEvent = nil + end +end + +function g_effects.cancelFade(widget) + removeEvent(widget.fadeEvent) + widget.fadeEvent = nil +end + +function g_effects.startBlink(widget, duration, interval, clickCancel) + duration = duration or 0 -- until stop is called + interval = interval or 500 + clickCancel = clickCancel or true + + removeEvent(widget.blinkEvent) + removeEvent(widget.blinkStopEvent) + + widget.blinkEvent = cycleEvent(function() + widget:setOn(not widget:isOn()) + end, interval) + + if duration > 0 then + widget.blinkStopEvent = scheduleEvent(function() + g_effects.stopBlink(widget) + end, duration) + end + + connect(widget, { onClick = g_effects.stopBlink }) +end + +function g_effects.stopBlink(widget) + disconnect(widget, { onClick = g_effects.stopBlink }) + removeEvent(widget.blinkEvent) + removeEvent(widget.blinkStopEvent) + widget.blinkEvent = nil + widget.blinkStopEvent = nil + widget:setOn(false) +end diff --git a/800OTClient/modules/corelib/ui/tooltip.lua b/800OTClient/modules/corelib/ui/tooltip.lua new file mode 100644 index 0000000..c390707 --- /dev/null +++ b/800OTClient/modules/corelib/ui/tooltip.lua @@ -0,0 +1,124 @@ +-- @docclass +g_tooltip = {} + +-- private variables +local toolTipLabel +local currentHoveredWidget + +-- private functions +local function moveToolTip(first) + if not first and (not toolTipLabel:isVisible() or toolTipLabel:getOpacity() < 0.1) then return end + + local pos = g_window.getMousePosition() + local windowSize = g_window.getSize() + local labelSize = toolTipLabel:getSize() + + pos.x = pos.x + 1 + pos.y = pos.y + 1 + + if windowSize.width - (pos.x + labelSize.width) < 10 then + pos.x = pos.x - labelSize.width - 3 + else + pos.x = pos.x + 10 + end + + if windowSize.height - (pos.y + labelSize.height) < 10 then + pos.y = pos.y - labelSize.height - 3 + else + pos.y = pos.y + 10 + end + + toolTipLabel:setPosition(pos) +end + +local function onWidgetHoverChange(widget, hovered) + if hovered then + if widget.tooltip and not g_mouse.isPressed() then + g_tooltip.display(widget.tooltip) + currentHoveredWidget = widget + end + else + if widget == currentHoveredWidget then + g_tooltip.hide() + currentHoveredWidget = nil + end + end +end + +local function onWidgetStyleApply(widget, styleName, styleNode) + if styleNode.tooltip then + widget.tooltip = styleNode.tooltip + end +end + +-- public functions +function g_tooltip.init() + connect(UIWidget, { onStyleApply = onWidgetStyleApply, + onHoverChange = onWidgetHoverChange}) + + addEvent(function() + toolTipLabel = g_ui.createWidget('UILabel', rootWidget) + toolTipLabel:setId('toolTip') + toolTipLabel:setBackgroundColor('#111111cc') + toolTipLabel:setTextAlign(AlignCenter) + toolTipLabel:hide() + end) +end + +function g_tooltip.terminate() + disconnect(UIWidget, { onStyleApply = onWidgetStyleApply, + onHoverChange = onWidgetHoverChange }) + + currentHoveredWidget = nil + toolTipLabel:destroy() + toolTipLabel = nil + + g_tooltip = nil +end + +function g_tooltip.display(text) + if text == nil or text:len() == 0 then return end + if not toolTipLabel then return end + + toolTipLabel:setText(text) + toolTipLabel:resizeToText() + toolTipLabel:resize(toolTipLabel:getWidth() + 4, toolTipLabel:getHeight() + 4) + toolTipLabel:show() + toolTipLabel:raise() + toolTipLabel:enable() + g_effects.fadeIn(toolTipLabel, 100) + moveToolTip(true) + + connect(rootWidget, { + onMouseMove = moveToolTip, + }) +end + +function g_tooltip.hide() + g_effects.fadeOut(toolTipLabel, 100) + + disconnect(rootWidget, { + onMouseMove = moveToolTip, + }) +end + + +-- @docclass UIWidget @{ + +-- UIWidget extensions +function UIWidget:setTooltip(text) + self.tooltip = text +end + +function UIWidget:removeTooltip() + self.tooltip = nil +end + +function UIWidget:getTooltip() + return self.tooltip +end + +-- @} + +g_tooltip.init() +connect(g_app, { onTerminate = g_tooltip.terminate }) diff --git a/800OTClient/modules/corelib/ui/uibutton.lua b/800OTClient/modules/corelib/ui/uibutton.lua new file mode 100644 index 0000000..8a7d125 --- /dev/null +++ b/800OTClient/modules/corelib/ui/uibutton.lua @@ -0,0 +1,12 @@ +-- @docclass +UIButton = extends(UIWidget, "UIButton") + +function UIButton.create() + local button = UIButton.internalCreate() + button:setFocusable(false) + return button +end + +function UIButton:onMouseRelease(pos, button) + return self:isPressed() +end diff --git a/800OTClient/modules/corelib/ui/uicheckbox.lua b/800OTClient/modules/corelib/ui/uicheckbox.lua new file mode 100644 index 0000000..195c593 --- /dev/null +++ b/800OTClient/modules/corelib/ui/uicheckbox.lua @@ -0,0 +1,13 @@ +-- @docclass +UICheckBox = extends(UIWidget, "UICheckBox") + +function UICheckBox.create() + local checkbox = UICheckBox.internalCreate() + checkbox:setFocusable(false) + checkbox:setTextAlign(AlignLeft) + return checkbox +end + +function UICheckBox:onClick() + self:setChecked(not self:isChecked()) +end diff --git a/800OTClient/modules/corelib/ui/uicombobox.lua b/800OTClient/modules/corelib/ui/uicombobox.lua new file mode 100644 index 0000000..51bbf2a --- /dev/null +++ b/800OTClient/modules/corelib/ui/uicombobox.lua @@ -0,0 +1,184 @@ +-- @docclass +UIComboBox = extends(UIWidget, "UIComboBox") + +function UIComboBox.create() + local combobox = UIComboBox.internalCreate() + combobox:setFocusable(false) + combobox.options = {} + combobox.currentIndex = -1 + combobox.mouseScroll = true + combobox.menuScroll = false + combobox.menuHeight = 100 + combobox.menuScrollStep = 0 + return combobox +end + +function UIComboBox:clearOptions() + self.options = {} + self.currentIndex = -1 + self:clearText() +end + +function UIComboBox:clear() + return self:clearOptions() +end + +function UIComboBox:getOptionsCount() + return #self.options +end + +function UIComboBox:isOption(text) + if not self.options then return false end + for i,v in ipairs(self.options) do + if v.text == text then + return true + end + end + return false +end + +function UIComboBox:setOption(text, dontSignal) + self:setCurrentOption(text, dontSignal) +end + +function UIComboBox:setCurrentOption(text, dontSignal) + if not self.options then return end + for i,v in ipairs(self.options) do + if v.text == text and self.currentIndex ~= i then + self.currentIndex = i + self:setText(text) + if not dontSignal then + signalcall(self.onOptionChange, self, text, v.data) + end + return + end + end +end + +function UIComboBox:updateCurrentOption(newText) + self.options[self.currentIndex].text = newText + self:setText(newText) +end + +function UIComboBox:setCurrentOptionByData(data, dontSignal) + if not self.options then return end + for i,v in ipairs(self.options) do + if v.data == data and self.currentIndex ~= i then + self.currentIndex = i + self:setText(v.text) + if not dontSignal then + signalcall(self.onOptionChange, self, v.text, v.data) + end + return + end + end +end + +function UIComboBox:setCurrentIndex(index, dontSignal) + if index >= 1 and index <= #self.options then + local v = self.options[index] + self.currentIndex = index + self:setText(v.text) + if not dontSignal then + signalcall(self.onOptionChange, self, v.text, v.data) + end + end +end + +function UIComboBox:getCurrentOption() + if table.haskey(self.options, self.currentIndex) then + return self.options[self.currentIndex] + end +end + +function UIComboBox:addOption(text, data) + table.insert(self.options, { text = text, data = data }) + local index = #self.options + if index == 1 then self:setCurrentOption(text) end + return index +end + +function UIComboBox:removeOption(text) + for i,v in ipairs(self.options) do + if v.text == text then + table.remove(self.options, i) + if self.currentIndex == i then + self:setCurrentIndex(1) + elseif self.currentIndex > i then + self.currentIndex = self.currentIndex - 1 + end + return + end + end +end + +function UIComboBox:onMousePress(mousePos, mouseButton) + local menu + if self.menuScroll then + menu = g_ui.createWidget(self:getStyleName() .. 'PopupScrollMenu') + menu:setHeight(self.menuHeight) + if self.menuScrollStep > 0 then + menu:setScrollbarStep(self.menuScrollStep) + end + else + menu = g_ui.createWidget(self:getStyleName() .. 'PopupMenu') + end + menu:setId(self:getId() .. 'PopupMenu') + for i,v in ipairs(self.options) do + menu:addOption(v.text, function() self:setCurrentOption(v.text) end) + end + menu:setWidth(self:getWidth()) + menu:display({ x = self:getX(), y = self:getY() + self:getHeight() }) + connect(menu, { onDestroy = function() self:setOn(false) end }) + self:setOn(true) + return true +end + +function UIComboBox:onMouseWheel(mousePos, direction) + if not self.mouseScroll or self.disableScroll then + return false + end + if direction == MouseWheelUp and self.currentIndex > 1 then + self:setCurrentIndex(self.currentIndex - 1) + elseif direction == MouseWheelDown and self.currentIndex < #self.options then + self:setCurrentIndex(self.currentIndex + 1) + end + return true +end + +function UIComboBox:onStyleApply(styleName, styleNode) + if styleNode.options then + for k,option in pairs(styleNode.options) do + self:addOption(option) + end + end + + if styleNode.data then + for k,data in pairs(styleNode.data) do + local option = self.options[k] + if option then + option.data = data + end + end + end + + for name,value in pairs(styleNode) do + if name == 'mouse-scroll' then + self.mouseScroll = value + elseif name == 'menu-scroll' then + self.menuScroll = value + elseif name == 'menu-height' then + self.menuHeight = value + elseif name == 'menu-scroll-step' then + self.menuScrollStep = value + end + end +end + +function UIComboBox:setMouseScroll(scroll) + self.mouseScroll = scroll +end + +function UIComboBox:canMouseScroll() + return self.mouseScroll +end \ No newline at end of file diff --git a/800OTClient/modules/corelib/ui/uiimageview.lua b/800OTClient/modules/corelib/ui/uiimageview.lua new file mode 100644 index 0000000..7a2e5fe --- /dev/null +++ b/800OTClient/modules/corelib/ui/uiimageview.lua @@ -0,0 +1,99 @@ +-- @docclass +UIImageView = extends(UIWidget, "UIImageView") + +function UIImageView.create() + local imageView = UIImageView.internalCreate() + imageView.zoom = 1 + imageView.minZoom = math.pow(10, -2) + imageView.maxZoom = math.pow(10, 2) + imageView:setClipping(true) + return imageView +end + +function UIImageView:getDefaultZoom() + local width = self:getWidth() + local height = self:getHeight() + local textureWidth = self:getImageTextureWidth() + local textureHeight = self:getImageTextureHeight() + local zoomX = width / textureWidth + local zoomY = height / textureHeight + return math.min(zoomX, zoomY) +end + +function UIImageView:getImagePosition(x, y) + x = x or self:getWidth() / 2 + y = y or self:getHeight() / 2 + local offsetX = self:getImageOffsetX() + local offsetY = self:getImageOffsetY() + local posX = (x - offsetX) / self.zoom + local posY = (y - offsetY) / self.zoom + return posX, posY +end + +function UIImageView:setImage(image) + self:setImageSource(image) + local zoom = self:getDefaultZoom() + self:setZoom(zoom) + self:center() +end + +function UIImageView:setZoom(zoom, x, y) + zoom = math.max(math.min(zoom, self.maxZoom), self.minZoom) + local posX, posY = self:getImagePosition(x, y) + local textureWidth = self:getImageTextureWidth() + local textureHeight = self:getImageTextureHeight() + local imageWidth = textureWidth * zoom + local imageHeight = textureHeight * zoom + self:setImageWidth(imageWidth) + self:setImageHeight(imageHeight) + self.zoom = zoom + self:move(posX, posY, x, y) +end + +function UIImageView:zoomIn(x, y) + local zoom = self.zoom * 1.1 + self:setZoom(zoom, x, y) +end + +function UIImageView:zoomOut(x, y) + local zoom = self.zoom / 1.1 + self:setZoom(zoom, x, y) +end + +function UIImageView:center() + self:move(self:getImageTextureWidth() / 2, self:getImageTextureHeight() / 2) +end + +function UIImageView:move(x, y, centerX, centerY) + x = math.max(math.min(x, self:getImageTextureWidth()), 0) + y = math.max(math.min(y, self:getImageTextureHeight()), 0) + local centerX = centerX or self:getWidth() / 2 + local centerY = centerY or self:getHeight() / 2 + local offsetX = centerX - x * self.zoom + local offsetY = centerY - y * self.zoom + self:setImageOffset({x=offsetX, y=offsetY}) +end + +function UIImageView:onDragEnter(pos) + return true +end + +function UIImageView:onDragMove(pos, moved) + local posX, posY = self:getImagePosition() + self:move(posX - moved.x / self.zoom, posY - moved.y / self.zoom) + return true +end + +function UIImageView:onDragLeave(widget, pos) + return true +end + +function UIImageView:onMouseWheel(mousePos, direction) + local x = mousePos.x - self:getX() + local y = mousePos.y - self:getY() + if direction == MouseWheelUp then + self:zoomIn(x, y) + elseif direction == MouseWheelDown then + self:zoomOut(x, y) + end +end diff --git a/800OTClient/modules/corelib/ui/uiinputbox.lua b/800OTClient/modules/corelib/ui/uiinputbox.lua new file mode 100644 index 0000000..1db361e --- /dev/null +++ b/800OTClient/modules/corelib/ui/uiinputbox.lua @@ -0,0 +1,114 @@ +if not UIWindow then dofile 'uiwindow' end + +-- @docclass +UIInputBox = extends(UIWindow, "UIInputBox") + +function UIInputBox.create(title, okCallback, cancelCallback) + local inputBox = UIInputBox.internalCreate() + + inputBox:setText(title) + inputBox.inputs = {} + inputBox.onEnter = function() + local results = {} + for _,func in pairs(inputBox.inputs) do + table.insert(results, func()) + end + okCallback(unpack(results)) + inputBox:destroy() + end + inputBox.onEscape = function() + if cancelCallback then + cancelCallback() + end + inputBox:destroy() + end + + return inputBox +end + +function UIInputBox:addLabel(text) + local label = g_ui.createWidget('InputBoxLabel', self) + label:setText(text) + return label +end + +function UIInputBox:addLineEdit(labelText, defaultText, maxLength) + if labelText then self:addLabel(labelText) end + local lineEdit = g_ui.createWidget('InputBoxLineEdit', self) + if defaultText then lineEdit:setText(defaultText) end + if maxLength then lineEdit:setMaxLength(maxLength) end + table.insert(self.inputs, function() return lineEdit:getText() end) + return lineEdit +end + +function UIInputBox:addTextEdit(labelText, defaultText, maxLength, visibleLines) + if labelText then self:addLabel(labelText) end + local textEdit = g_ui.createWidget('InputBoxTextEdit', self) + if defaultText then textEdit:setText(defaultText) end + if maxLength then textEdit:setMaxLength(maxLength) end + visibleLines = visibleLines or 1 + textEdit:setHeight(textEdit:getHeight() * visibleLines) + table.insert(self.inputs, function() return textEdit:getText() end) + return textEdit +end + +function UIInputBox:addCheckBox(text, checked) + local checkBox = g_ui.createWidget('InputBoxCheckBox', self) + checkBox:setText(text) + checkBox:setChecked(checked) + table.insert(self.inputs, function() return checkBox:isChecked() end) + return checkBox +end + +function UIInputBox:addComboBox(labelText, ...) + if labelText then self:addLabel(labelText) end + local comboBox = g_ui.createWidget('InputBoxComboBox', self) + local options = {...} + for i=1,#options do + comboBox:addOption(options[i]) + end + table.insert(self.inputs, function() return comboBox:getCurrentOption() end) + return comboBox +end + +function UIInputBox:addSpinBox(labelText, minimum, maximum, value, step) + if labelText then self:addLabel(labelText) end + local spinBox = g_ui.createWidget('InputBoxSpinBox', self) + spinBox:setMinimum(minimum) + spinBox:setMaximum(maximum) + spinBox:setValue(value) + spinBox:setStep(step) + table.insert(self.inputs, function() return spinBox:getValue() end) + return spinBox +end + +function UIInputBox:display(okButtonText, cancelButtonText) + okButtonText = okButtonText or tr('Ok') + cancelButtonText = cancelButtonText or tr('Cancel') + + local buttonsWidget = g_ui.createWidget('InputBoxButtonsPanel', self) + local okButton = g_ui.createWidget('InputBoxButton', buttonsWidget) + okButton:setText(okButtonText) + okButton.onClick = self.onEnter + + local cancelButton = g_ui.createWidget('InputBoxButton', buttonsWidget) + cancelButton:setText(cancelButtonText) + cancelButton.onClick = self.onEscape + + buttonsWidget:setHeight(okButton:getHeight()) + + rootWidget:addChild(self) + self:setStyle('InputBoxWindow') +end + +function displayTextInputBox(title, label, okCallback, cancelCallback) + local inputBox = UIInputBox.create(title, okCallback, cancelCallback) + inputBox:addLineEdit(label) + inputBox:display() +end + +function displayNumberInputBox(title, label, okCallback, cancelCallback, min, max, value, step) + local inputBox = UIInputBox.create(title, okCallback, cancelCallback) + inputBox:addSpinBox(label, min, max, value, step) + inputBox:display() +end diff --git a/800OTClient/modules/corelib/ui/uilabel.lua b/800OTClient/modules/corelib/ui/uilabel.lua new file mode 100644 index 0000000..ecd72bc --- /dev/null +++ b/800OTClient/modules/corelib/ui/uilabel.lua @@ -0,0 +1,10 @@ +-- @docclass +UILabel = extends(UIWidget, "UILabel") + +function UILabel.create() + local label = UILabel.internalCreate() + label:setPhantom(true) + label:setFocusable(false) + label:setTextAlign(AlignLeft) + return label +end diff --git a/800OTClient/modules/corelib/ui/uimessagebox.lua b/800OTClient/modules/corelib/ui/uimessagebox.lua new file mode 100644 index 0000000..62f84a2 --- /dev/null +++ b/800OTClient/modules/corelib/ui/uimessagebox.lua @@ -0,0 +1,96 @@ +if not UIWindow then dofile 'uiwindow' end + +-- @docclass +UIMessageBox = extends(UIWindow, "UIMessageBox") + +-- messagebox cannot be created from otui files +UIMessageBox.create = nil + +function UIMessageBox.display(title, message, buttons, onEnterCallback, onEscapeCallback) + local messageBox = UIMessageBox.internalCreate() + rootWidget:addChild(messageBox) + + messageBox:setStyle('MainWindow') + messageBox:setText(title) + + local messageLabel = g_ui.createWidget('MessageBoxLabel', messageBox) + messageLabel:setText(message) + + local buttonsWidth = 0 + local buttonsHeight = 0 + + local anchor = AnchorRight + if buttons.anchor then anchor = buttons.anchor end + + local buttonHolder = g_ui.createWidget('MessageBoxButtonHolder', messageBox) + buttonHolder:addAnchor(anchor, 'parent', anchor) + + for i=1,#buttons do + local button = messageBox:addButton(buttons[i].text, buttons[i].callback) + if i == 1 then + button:setMarginLeft(0) + button:addAnchor(AnchorBottom, 'parent', AnchorBottom) + button:addAnchor(AnchorLeft, 'parent', AnchorLeft) + buttonsHeight = button:getHeight() + else + button:addAnchor(AnchorBottom, 'prev', AnchorBottom) + button:addAnchor(AnchorLeft, 'prev', AnchorRight) + end + buttonsWidth = buttonsWidth + button:getWidth() + button:getMarginLeft() + end + + buttonHolder:setWidth(buttonsWidth) + buttonHolder:setHeight(buttonsHeight) + + if onEnterCallback then connect(messageBox, { onEnter = onEnterCallback }) end + if onEscapeCallback then connect(messageBox, { onEscape = onEscapeCallback }) end + + messageBox:setWidth(math.max(messageLabel:getWidth(), messageBox:getTextSize().width, buttonHolder:getWidth()) + messageBox:getPaddingLeft() + messageBox:getPaddingRight()) + messageBox:setHeight(messageLabel:getHeight() + messageBox:getPaddingTop() + messageBox:getPaddingBottom() + buttonHolder:getHeight() + buttonHolder:getMarginTop()) + return messageBox +end + +function displayInfoBox(title, message) + local messageBox + local defaultCallback = function() messageBox:ok() end + messageBox = UIMessageBox.display(title, message, {{text='Ok', callback=defaultCallback}}, defaultCallback, defaultCallback) + return messageBox +end + +function displayErrorBox(title, message) + local messageBox + local defaultCallback = function() messageBox:ok() end + messageBox = UIMessageBox.display(title, message, {{text='Ok', callback=defaultCallback}}, defaultCallback, defaultCallback) + return messageBox +end + +function displayCancelBox(title, message) + local messageBox + local defaultCallback = function() messageBox:cancel() end + messageBox = UIMessageBox.display(title, message, {{text='Cancel', callback=defaultCallback}}, defaultCallback, defaultCallback) + return messageBox +end + +function displayGeneralBox(title, message, buttons, onEnterCallback, onEscapeCallback) + return UIMessageBox.display(title, message, buttons, onEnterCallback, onEscapeCallback) +end + +function UIMessageBox:addButton(text, callback) + local buttonHolder = self:getChildById('buttonHolder') + local button = g_ui.createWidget('MessageBoxButton', buttonHolder) + button:setText(text) + connect(button, { onClick = callback }) + return button +end + +function UIMessageBox:ok() + signalcall(self.onOk, self) + self.onOk = nil + self:destroy() +end + +function UIMessageBox:cancel() + signalcall(self.onCancel, self) + self.onCancel = nil + self:destroy() +end diff --git a/800OTClient/modules/corelib/ui/uiminiwindow.lua b/800OTClient/modules/corelib/ui/uiminiwindow.lua new file mode 100644 index 0000000..2c775e6 --- /dev/null +++ b/800OTClient/modules/corelib/ui/uiminiwindow.lua @@ -0,0 +1,466 @@ +-- @docclass +UIMiniWindow = extends(UIWindow, "UIMiniWindow") + +function UIMiniWindow.create() + local miniwindow = UIMiniWindow.internalCreate() + miniwindow.UIMiniWindowContainer = true + return miniwindow +end + +function UIMiniWindow:open(dontSave) + self:setVisible(true) + + if not dontSave then + self:setSettings({closed = false}) + end + + signalcall(self.onOpen, self) +end + +function UIMiniWindow:close(dontSave) + if not self:isExplicitlyVisible() then return end + if self.forceOpen then return end + self:setVisible(false) + + if not dontSave then + self:setSettings({closed = true}) + end + + signalcall(self.onClose, self) +end + +function UIMiniWindow:minimize(dontSave) + self:setOn(true) + self:getChildById('contentsPanel'):hide() + self:getChildById('miniwindowScrollBar'):hide() + self:getChildById('bottomResizeBorder'):hide() + if self.minimizeButton then + self.minimizeButton:setOn(true) + end + self.maximizedHeight = self:getHeight() + self:setHeight(self.minimizedHeight) + + if not dontSave then + self:setSettings({minimized = true}) + end + + signalcall(self.onMinimize, self) +end + +function UIMiniWindow:maximize(dontSave) + self:setOn(false) + self:getChildById('contentsPanel'):show() + self:getChildById('miniwindowScrollBar'):show() + self:getChildById('bottomResizeBorder'):show() + if self.minimizeButton then + self.minimizeButton:setOn(false) + end + self:setHeight(self:getSettings('height') or self.maximizedHeight) + + if not dontSave then + self:setSettings({minimized = false}) + end + + local parent = self:getParent() + if parent and parent:getClassName() == 'UIMiniWindowContainer' then + parent:fitAll(self) + end + + signalcall(self.onMaximize, self) +end + +function UIMiniWindow:lock(dontSave) + local lockButton = self:getChildById('lockButton') + if lockButton then + lockButton:setOn(true) + end + self:setDraggable(false) + if not dontsave then + self:setSettings({locked = true}) + end + + signalcall(self.onLockChange, self) +end + +function UIMiniWindow:unlock(dontSave) + local lockButton = self:getChildById('lockButton') + if lockButton then + lockButton:setOn(false) + end + self:setDraggable(true) + if not dontsave then + self:setSettings({locked = false}) + end + signalcall(self.onLockChange, self) +end + +function UIMiniWindow:setup() + self:getChildById('closeButton').onClick = + function() + self:close() + end + if self.forceOpen then + if self.closeButton then + self.closeButton:hide() + end + end + + if(self.minimizeButton) then + self.minimizeButton.onClick = + function() + if self:isOn() then + self:maximize() + else + self:minimize() + end + end + end + + local lockButton = self:getChildById('lockButton') + if lockButton then + lockButton.onClick = + function () + if self:isDraggable() then + self:lock() + else + self:unlock() + end + end + end + + self:getChildById('miniwindowTopBar').onDoubleClick = + function() + if self:isOn() then + self:maximize() + else + self:minimize() + end + end + self:getChildById('bottomResizeBorder').onDoubleClick = function() + local resizeBorder = self:getChildById('bottomResizeBorder') + self:setHeight(resizeBorder:getMinimum()) + end + + local oldParent = self:getParent() + + + local settings = {} + if g_settings.getNodeSize('MiniWindows') < 50 then + settings = g_settings.getNode('MiniWindows') + end + + if settings then + local selfSettings = settings[self:getId()] + if selfSettings then + if selfSettings.parentId then + local parent = rootWidget:recursiveGetChildById(selfSettings.parentId) + if parent then + if parent:getClassName() == 'UIMiniWindowContainer' and selfSettings.index and parent:isOn() then + self.miniIndex = selfSettings.index + parent:scheduleInsert(self, selfSettings.index) + elseif selfSettings.position then + self:setParent(parent, true) + self:setPosition(topoint(selfSettings.position)) + end + end + end + + if selfSettings.minimized then + self:minimize(true) + else + if selfSettings.height and self:isResizeable() then + self:setHeight(selfSettings.height) + elseif selfSettings.height and not self:isResizeable() then + self:eraseSettings({height = true}) + end + end + if selfSettings.closed and not self.forceOpen and not self.containerWindow then + self:close(true) + end + + if selfSettings.locked then + self:lock(true) + end + else + if not self.forceOpen and self.autoOpen ~= nil and (self.autoOpen == 0 or self.autoOpen == false) and not self.containerWindow then + self:close(true) + end + end + end + + local newParent = self:getParent() + + self.miniLoaded = true + + if self.save then + if oldParent and oldParent:getClassName() == 'UIMiniWindowContainer' and not self.containerWindow then + addEvent(function() oldParent:order() end) + end + if newParent and newParent:getClassName() == 'UIMiniWindowContainer' and newParent ~= oldParent then + addEvent(function() newParent:order() end) + end + end + + self:fitOnParent() +end + +function UIMiniWindow:onVisibilityChange(visible) + self:fitOnParent() +end + +function UIMiniWindow:onDragEnter(mousePos) + local parent = self:getParent() + if not parent then return false end + + if parent:getClassName() == 'UIMiniWindowContainer' then + local containerParent = parent:getParent():getParent() + parent:removeChild(self) + containerParent:addChild(self) + parent:saveChildren() + end + + local oldPos = self:getPosition() + self.movingReference = { x = mousePos.x - oldPos.x, y = mousePos.y - oldPos.y } + self:setPosition(oldPos) + self.free = true + return true +end + +function UIMiniWindow:onDragLeave(droppedWidget, mousePos) + if self.movedWidget then + self.setMovedChildMargin(self.movedOldMargin or 0) + self.movedWidget = nil + self.setMovedChildMargin = nil + self.movedOldMargin = nil + self.movedIndex = nil + end + + UIWindow:onDragLeave(self, droppedWidget, mousePos) + self:saveParent(self:getParent()) +end + +function UIMiniWindow:onDragMove(mousePos, mouseMoved) + local oldMousePosY = mousePos.y - mouseMoved.y + local children = rootWidget:recursiveGetChildrenByMarginPos(mousePos) + local overAnyWidget = false + for i=1,#children do + local child = children[i] + if child:getParent():getClassName() == 'UIMiniWindowContainer' then + overAnyWidget = true + + local childCenterY = child:getY() + child:getHeight() / 2 + if child == self.movedWidget and mousePos.y < childCenterY and oldMousePosY < childCenterY then + break + end + + if self.movedWidget then + self.setMovedChildMargin(self.movedOldMargin or 0) + self.setMovedChildMargin = nil + end + + if mousePos.y < childCenterY then + self.movedOldMargin = child:getMarginTop() + self.setMovedChildMargin = function(v) child:setMarginTop(v) end + self.movedIndex = 0 + else + self.movedOldMargin = child:getMarginBottom() + self.setMovedChildMargin = function(v) child:setMarginBottom(v) end + self.movedIndex = 1 + end + + self.movedWidget = child + self.setMovedChildMargin(self:getHeight()) + break + end + end + + if not overAnyWidget and self.movedWidget then + self.setMovedChildMargin(self.movedOldMargin or 0) + self.movedWidget = nil + end + + return UIWindow.onDragMove(self, mousePos, mouseMoved) +end + +function UIMiniWindow:onMousePress() + local parent = self:getParent() + if not parent then return false end + if parent:getClassName() ~= 'UIMiniWindowContainer' then + self:raise() + return true + end +end + +function UIMiniWindow:onFocusChange(focused) + if not focused then return end + local parent = self:getParent() + if parent and parent:getClassName() ~= 'UIMiniWindowContainer' then + self:raise() + end +end + +function UIMiniWindow:onHeightChange(height) + if not self:isOn() then + self:setSettings({height = height}) + end + self:fitOnParent() +end + +function UIMiniWindow:getSettings(name) + if not self.save then return nil end + local settings = g_settings.getNode('MiniWindows') + if settings then + local selfSettings = settings[self:getId()] + if selfSettings then + return selfSettings[name] + end + end + return nil +end + +function UIMiniWindow:setSettings(data) + if not self.save then return end + + local settings = g_settings.getNode('MiniWindows') + if not settings then + settings = {} + end + + local id = self:getId() + if not settings[id] then + settings[id] = {} + end + + for key,value in pairs(data) do + settings[id][key] = value + end + + g_settings.setNode('MiniWindows', settings) +end + +function UIMiniWindow:eraseSettings(data) + if not self.save then return end + + local settings = g_settings.getNode('MiniWindows') + if not settings then + settings = {} + end + + local id = self:getId() + if not settings[id] then + settings[id] = {} + end + + for key,value in pairs(data) do + settings[id][key] = nil + end + + g_settings.setNode('MiniWindows', settings) +end + +function UIMiniWindow:clearSettings() + if not self.save then return end + + local settings = g_settings.getNode('MiniWindows') + if not settings then + settings = {} + end + + local id = self:getId() + settings[id] = {} + + g_settings.setNode('MiniWindows', settings) +end + +function UIMiniWindow:saveParent(parent) + local parent = self:getParent() + if parent then + if parent:getClassName() == 'UIMiniWindowContainer' then + parent:saveChildren() + else + self:saveParentPosition(parent:getId(), self:getPosition()) + end + end +end + +function UIMiniWindow:saveParentPosition(parentId, position) + local selfSettings = {} + selfSettings.parentId = parentId + selfSettings.position = pointtostring(position) + self:setSettings(selfSettings) +end + +function UIMiniWindow:saveParentIndex(parentId, index) + local selfSettings = {} + selfSettings.parentId = parentId + selfSettings.index = index + self:setSettings(selfSettings) + self.miniIndex = index +end + +function UIMiniWindow:disableResize() + self:getChildById('bottomResizeBorder'):disable() +end + +function UIMiniWindow:enableResize() + self:getChildById('bottomResizeBorder'):enable() +end + +function UIMiniWindow:fitOnParent() + local parent = self:getParent() + if self:isVisible() and parent and parent:getClassName() == 'UIMiniWindowContainer' then + parent:fitAll(self) + end +end + +function UIMiniWindow:setParent(parent, dontsave) + UIWidget.setParent(self, parent) + if not dontsave then + self:saveParent(parent) + end + self:fitOnParent() +end + +function UIMiniWindow:setHeight(height) + UIWidget.setHeight(self, height) + signalcall(self.onHeightChange, self, height) +end + +function UIMiniWindow:setContentHeight(height) + local contentsPanel = self:getChildById('contentsPanel') + local minHeight = contentsPanel:getMarginTop() + contentsPanel:getMarginBottom() + contentsPanel:getPaddingTop() + contentsPanel:getPaddingBottom() + + local resizeBorder = self:getChildById('bottomResizeBorder') + resizeBorder:setParentSize(minHeight + height) +end + +function UIMiniWindow:setContentMinimumHeight(height) + local contentsPanel = self:getChildById('contentsPanel') + local minHeight = contentsPanel:getMarginTop() + contentsPanel:getMarginBottom() + contentsPanel:getPaddingTop() + contentsPanel:getPaddingBottom() + + local resizeBorder = self:getChildById('bottomResizeBorder') + resizeBorder:setMinimum(minHeight + height) +end + +function UIMiniWindow:setContentMaximumHeight(height) + local contentsPanel = self:getChildById('contentsPanel') + local minHeight = contentsPanel:getMarginTop() + contentsPanel:getMarginBottom() + contentsPanel:getPaddingTop() + contentsPanel:getPaddingBottom() + + local resizeBorder = self:getChildById('bottomResizeBorder') + resizeBorder:setMaximum(minHeight + height) +end + +function UIMiniWindow:getMinimumHeight() + local resizeBorder = self:getChildById('bottomResizeBorder') + return resizeBorder:getMinimum() +end + +function UIMiniWindow:getMaximumHeight() + local resizeBorder = self:getChildById('bottomResizeBorder') + return resizeBorder:getMaximum() +end + +function UIMiniWindow:isResizeable() + local resizeBorder = self:getChildById('bottomResizeBorder') + return resizeBorder:isExplicitlyVisible() and resizeBorder:isEnabled() +end diff --git a/800OTClient/modules/corelib/ui/uiminiwindowcontainer.lua b/800OTClient/modules/corelib/ui/uiminiwindowcontainer.lua new file mode 100644 index 0000000..6c6eea1 --- /dev/null +++ b/800OTClient/modules/corelib/ui/uiminiwindowcontainer.lua @@ -0,0 +1,228 @@ +-- @docclass +UIMiniWindowContainer = extends(UIWidget, "UIMiniWindowContainer") + +function UIMiniWindowContainer.create() + local container = UIMiniWindowContainer.internalCreate() + container.scheduledWidgets = {} + container:setFocusable(false) + container:setPhantom(true) + return container +end + +-- TODO: connect to window onResize event +-- TODO: try to resize another widget? +-- TODO: try to find another panel? +function UIMiniWindowContainer:fitAll(noRemoveChild) + if not self:isVisible() then + return + end + + if not noRemoveChild then + local children = self:getChildren() + if #children > 0 then + noRemoveChild = children[#children] + else + return + end + end + + local sumHeight = 0 + local children = self:getChildren() + for i=1,#children do + if children[i]:isVisible() then + sumHeight = sumHeight + children[i]:getHeight() + end + end + + local selfHeight = self:getHeight() - (self:getPaddingTop() + self:getPaddingBottom()) + if sumHeight <= selfHeight then + return + end + + local removeChildren = {} + + -- try to resize noRemoveChild + local maximumHeight = selfHeight - (sumHeight - noRemoveChild:getHeight()) + if noRemoveChild:isResizeable() and noRemoveChild:getMinimumHeight() <= maximumHeight then + sumHeight = sumHeight - noRemoveChild:getHeight() + maximumHeight + addEvent(function() noRemoveChild:setHeight(maximumHeight) end) + end + + -- try to remove no-save widget + for i=#children,1,-1 do + if sumHeight <= selfHeight then + break + end + + local child = children[i] + if child ~= noRemoveChild and not child.save then + local childHeight = child:getHeight() + sumHeight = sumHeight - childHeight + table.insert(removeChildren, child) + end + end + + -- try to remove save widget, not forceOpen + for i=#children,1,-1 do + if sumHeight <= selfHeight then + break + end + + local child = children[i] + if child ~= noRemoveChild and child:isVisible() and not child.forceOpen then + local childHeight = child:getHeight() + sumHeight = sumHeight - childHeight + table.insert(removeChildren, child) + end + end + + -- try to remove save widget + for i=#children,1,-1 do + if sumHeight <= selfHeight then + break + end + + local child = children[i] + if child ~= noRemoveChild and child:isVisible() then + local childHeight = child:getHeight() - 50 + sumHeight = sumHeight - childHeight + table.insert(removeChildren, child) + end + end + + -- close widgets + for i=1,#removeChildren do + if removeChildren[i].forceOpen then + removeChildren[i]:minimize(true) + else + removeChildren[i]:close() + end + end +end + +function UIMiniWindowContainer:onDrop(widget, mousePos) + if widget.UIMiniWindowContainer then + local oldParent = widget:getParent() + if oldParent == self then + return true + end + + if oldParent then + oldParent:removeChild(widget) + end + + if widget.movedWidget then + local index = self:getChildIndex(widget.movedWidget) + self:insertChild(index + widget.movedIndex, widget) + else + self:addChild(widget) + end + + self:fitAll(widget) + return true + end +end + +function UIMiniWindowContainer:moveTo(newPanel) + if not newPanel or newPanel == self then + return + end + local children = self:getChildByIndex(1) + while children do + newPanel:addChild(children) + children = self:getChildByIndex(1) + end + newPanel:fitAll() +end + +function UIMiniWindowContainer:swapInsert(widget, index) + local oldParent = widget:getParent() + local oldIndex = self:getChildIndex(widget) + + if oldParent == self and oldIndex ~= index then + local oldWidget = self:getChildByIndex(index) + if oldWidget then + self:removeChild(oldWidget) + self:insertChild(oldIndex, oldWidget) + end + self:removeChild(widget) + self:insertChild(index, widget) + end +end + +function UIMiniWindowContainer:scheduleInsert(widget, index) + if index - 1 > self:getChildCount() then + if self.scheduledWidgets[index] then + pdebug('replacing scheduled widget id ' .. widget:getId()) + end + self.scheduledWidgets[index] = widget + else + local oldParent = widget:getParent() + if oldParent ~= self then + if oldParent then + oldParent:removeChild(widget) + end + self:insertChild(index, widget) + + while true do + local placed = false + for nIndex,nWidget in pairs(self.scheduledWidgets) do + if nIndex - 1 <= self:getChildCount() then + local oldParent = nWidget:getParent() + if oldParent ~= self then + if oldParent then + oldParent:removeChild(nWidget) + end + self:insertChild(nIndex, nWidget) + else + self:moveChildToIndex(nWidget, nIndex) + end + self.scheduledWidgets[nIndex] = nil + placed = true + break + end + end + if not placed then break end + end + end + end +end + +function UIMiniWindowContainer:order() + local children = self:getChildren() + for i=1,#children do + if not children[i].miniLoaded then return end + end + + table.sort(children, function(a, b) + local indexA = a.miniIndex or a.autoOpen or 999 + local indexB = b.miniIndex or b.autoOpen or 999 + return indexA < indexB + end) + + self:reorderChildren(children) + local ignoreIndex = 0 + for i=1,#children do + if children[i].save then + children[i].miniIndex = i - ignoreIndex + else + ignoreIndex = ignoreIndex + 1 + end + end +end + +function UIMiniWindowContainer:saveChildren() + local children = self:getChildren() + local ignoreIndex = 0 + for i=1,#children do + if children[i].save then + children[i]:saveParentIndex(self:getId(), i - ignoreIndex) + else + ignoreIndex = ignoreIndex + 1 + end + end +end + +function UIMiniWindowContainer:onGeometryChange() + self:fitAll() +end \ No newline at end of file diff --git a/800OTClient/modules/corelib/ui/uimovabletabbar.lua b/800OTClient/modules/corelib/ui/uimovabletabbar.lua new file mode 100644 index 0000000..c3e283e --- /dev/null +++ b/800OTClient/modules/corelib/ui/uimovabletabbar.lua @@ -0,0 +1,505 @@ +-- @docclass +UIMoveableTabBar = extends(UIWidget, "UIMoveableTabBar") + +-- private functions +local function onTabClick(tab) + tab.tabBar:selectTab(tab) +end + +local function updateMargins(tabBar) + if #tabBar.tabs == 0 then return end + + local currentMargin = 0 + for i = 1, #tabBar.tabs do + tabBar.tabs[i]:setMarginLeft(currentMargin) + currentMargin = currentMargin + tabBar.tabSpacing + tabBar.tabs[i]:getWidth() + end +end + +local function updateNavigation(tabBar) + if tabBar.prevNavigation then + if #tabBar.preTabs > 0 or table.find(tabBar.tabs, tabBar.currentTab) ~= 1 then + tabBar.prevNavigation:enable() + else + tabBar.prevNavigation:disable() + end + end + + if tabBar.nextNavigation then + if #tabBar.postTabs > 0 or table.find(tabBar.tabs, tabBar.currentTab) ~= #tabBar.tabs then + tabBar.nextNavigation:enable() + else + tabBar.nextNavigation:disable() + end + end +end + +local function updateIndexes(tabBar, tab, xoff) + local tabs = tabBar.tabs + local currentMargin = 0 + local prevIndex = table.find(tabs, tab) + local newIndex = prevIndex + local xmid = xoff + tab:getWidth()/2 + for i = 1, #tabs do + local nextTab = tabs[i] + if xmid >= currentMargin + nextTab:getWidth()/2 then + newIndex = table.find(tabs, nextTab) + end + currentMargin = currentMargin + tabBar.tabSpacing * (i - 1) + tabBar.tabs[i]:getWidth() + end + if newIndex ~= prevIndex then + table.remove(tabs, table.find(tabs, tab)) + table.insert(tabs, newIndex, tab) + end + updateNavigation(tabBar) +end + +local function getMaxMargin(tabBar, tab) + if #tabBar.tabs == 0 then return 0 end + + local maxMargin = 0 + for i = 1, #tabBar.tabs do + if tabBar.tabs[i] ~= tab then + maxMargin = maxMargin + tabBar.tabs[i]:getWidth() + end + end + return maxMargin + tabBar.tabSpacing * (#tabBar.tabs - 1) +end + +local function updateTabs(tabBar) + if #tabBar.postTabs > 0 then + local i = 1 + while i <= #tabBar.postTabs do + local tab = tabBar.postTabs[i] + if getMaxMargin(tabBar) + tab:getWidth() > tabBar:getWidth() then + break + end + + table.remove(tabBar.postTabs, i) + table.insert(tabBar.tabs, tab) + tab:setVisible(true) + end + end + if #tabBar.preTabs > 0 then + for i = #tabBar.preTabs, 1, -1 do + local tab = tabBar.preTabs[i] + if getMaxMargin(tabBar) + tab:getWidth() > tabBar:getWidth() then + break + end + + table.remove(tabBar.preTabs, i) + table.insert(tabBar.tabs, 1, tab) + tab:setVisible(true) + end + end + updateNavigation(tabBar) + updateMargins(tabBar) + if not tabBar.currentTab and #tabBar.tabs > 0 then + tabBar:selectTab(tabBar.tabs[1]) + end +end + +local function hideTabs(tabBar, fromBack, toArray, width) + while #tabBar.tabs > 0 and getMaxMargin(tabBar) + width > tabBar:getWidth() do + local index = fromBack and #tabBar.tabs or 1 + local tab = tabBar.tabs[index] + table.remove(tabBar.tabs, index) + if fromBack then + table.insert(toArray, 1, tab) + else + table.insert(toArray, tab) + end + if tabBar.currentTab == tab then + if #tabBar.tabs > 0 then + tabBar:selectTab(tabBar.tabs[#tabBar.tabs]) + else + tabBar.currentTab:setChecked(false) + tabBar.currentTab = nil + end + end + tab:setVisible(false) + end +end + +local function showPreTab(tabBar) + if #tabBar.preTabs == 0 then + return nil + end + + local tmpTab = tabBar.preTabs[#tabBar.preTabs] + hideTabs(tabBar, true, tabBar.postTabs, tmpTab:getWidth()) + + table.remove(tabBar.preTabs, #tabBar.preTabs) + table.insert(tabBar.tabs, 1, tmpTab) + tmpTab:setVisible(true) + return tmpTab +end + +local function showPostTab(tabBar) + if #tabBar.postTabs == 0 then + return nil + end + + local tmpTab = tabBar.postTabs[1] + hideTabs(tabBar, false, tabBar.preTabs, tmpTab:getWidth()) + + table.remove(tabBar.postTabs, 1) + table.insert(tabBar.tabs, tmpTab) + tmpTab:setVisible(true) + return tmpTab +end + +local function onTabMousePress(tab, mousePos, mouseButton) + if mouseButton == MouseRightButton then + if tab.menuCallback then tab.menuCallback(tab, mousePos, mouseButton) end + return true + end +end + +local function onTabDragEnter(tab, mousePos) + tab:raise() + tab.hotSpot = mousePos.x - tab:getMarginLeft() + tab.tabBar.selected = tab + return true +end + +local function onTabDragLeave(tab) + updateMargins(tab.tabBar) + tab.tabBar.selected = nil + return true +end + +local function onTabDragMove(tab, mousePos, mouseMoved) + if tab == tab.tabBar.selected then + local xoff = mousePos.x - tab.hotSpot + + -- update indexes + updateIndexes(tab.tabBar, tab, xoff) + updateIndexes(tab.tabBar, tab, xoff) + + -- update margins + updateMargins(tab.tabBar) + xoff = math.max(xoff, 0) + xoff = math.min(xoff, getMaxMargin(tab.tabBar, tab)) + tab:setMarginLeft(xoff) + end +end + +local function tabBlink(tab, step) + local step = step or 0 + tab:setOn(not tab:isOn()) + + removeEvent(tab.blinkEvent) + if step < 4 then + tab.blinkEvent = scheduleEvent(function() tabBlink(tab, step+1) end, 500) + else + tab:setOn(true) + tab.blinkEvent = nil + end +end + +-- public functions +function UIMoveableTabBar.create() + local tabbar = UIMoveableTabBar.internalCreate() + tabbar:setFocusable(false) + tabbar.tabs = {} + tabbar.selected = nil -- dragged tab + tabbar.tabSpacing = 0 + tabbar.tabsMoveable = false + tabbar.preTabs = {} + tabbar.postTabs = {} + tabbar.prevNavigation = nil + tabbar.nextNavigation = nil + tabbar.onGeometryChange = function() + hideTabs(tabbar, true, tabbar.postTabs, 0) + updateTabs(tabbar) + end + return tabbar +end + +function UIMoveableTabBar:onDestroy() + if self.prevNavigation then + self.prevNavigation:disable() + end + + if self.nextNavigation then + self.nextNavigation:disable() + end + + self.nextNavigation = nil + self.prevNavigation = nil +end + +function UIMoveableTabBar:setContentWidget(widget) + self.contentWidget = widget + if #self.tabs > 0 then + self.contentWidget:addChild(self.tabs[1].tabPanel) + end +end + +function UIMoveableTabBar:setTabSpacing(tabSpacing) + self.tabSpacing = tabSpacing + updateMargins(self) +end + +function UIMoveableTabBar:addTab(text, panel, menuCallback) + if panel == nil then + panel = g_ui.createWidget(self:getStyleName() .. 'Panel') + panel:setId('tabPanel') + end + + local tab = g_ui.createWidget(self:getStyleName() .. 'Button', self) + panel.isTab = true + tab.tabPanel = panel + tab.tabBar = self + tab:setId('tab') + tab:setDraggable(self.tabsMoveable) + tab:setText(text) + tab:setWidth(tab:getTextSize().width + tab:getPaddingLeft() + tab:getPaddingRight()) + tab.menuCallback = menuCallback or nil + tab.onClick = onTabClick + tab.onMousePress = onTabMousePress + tab.onDragEnter = onTabDragEnter + tab.onDragLeave = onTabDragLeave + tab.onDragMove = onTabDragMove + tab.onDestroy = function() tab.tabPanel:destroy() end + + if #self.tabs == 0 then + self:selectTab(tab) + tab:setMarginLeft(0) + table.insert(self.tabs, tab) + else + local newMargin = self.tabSpacing * #self.tabs + for i = 1, #self.tabs do + newMargin = newMargin + self.tabs[i]:getWidth() + end + tab:setMarginLeft(newMargin) + + hideTabs(self, true, self.postTabs, tab:getWidth()) + table.insert(self.tabs, tab) + if #self.tabs == 1 then + self:selectTab(tab) + end + updateMargins(self) + end + + updateNavigation(self) + return tab +end + +-- Additional function to move the tab by lua +function UIMoveableTabBar:moveTab(tab, units) + local index = table.find(self.tabs, tab) + if index == nil then return end + + local focus = false + if self.currentTab == tab then + self:selectPrevTab() + focus = true + end + + table.remove(self.tabs, index) + + local newIndex = math.min(#self.tabs+1, math.max(index + units, 1)) + table.insert(self.tabs, newIndex, tab) + if focus then self:selectTab(tab) end + updateMargins(self) + return newIndex +end + +function UIMoveableTabBar:onStyleApply(styleName, styleNode) + if styleNode['movable'] then + self.tabsMoveable = styleNode['movable'] + end + if styleNode['tab-spacing'] then + self:setTabSpacing(styleNode['tab-spacing']) + end +end + +function UIMoveableTabBar:clearTabs() + while #self.tabs > 0 do + self:removeTab(self.tabs[#self.tabs]) + end +end + +function UIMoveableTabBar:removeTab(tab) + local tabTables = {self.tabs, self.preTabs, self.postTabs} + local index = nil + local tabTable = nil + for i = 1, #tabTables do + index = table.find(tabTables[i], tab) + if index ~= nil then + tabTable = tabTables[i] + break + end + end + + if tabTable == nil then + return + end + table.remove(tabTable, index) + if self.currentTab == tab then + self:selectPrevTab() + if #self.tabs == 1 then + self.currentTab = nil + end + end + if tab.blinkEvent then + removeEvent(tab.blinkEvent) + end + updateTabs(self) + tab:destroy() +end + +function UIMoveableTabBar:getTab(text) + for k,tab in pairs(self.tabs) do + if tab:getText():lower() == text:lower() then + return tab + end + end + for k,tab in pairs(self.preTabs) do + if tab:getText():lower() == text:lower() then + return tab + end + end + for k,tab in pairs(self.postTabs) do + if tab:getText():lower() == text:lower() then + return tab + end + end +end + +function UIMoveableTabBar:selectTab(tab) + if self.currentTab == tab then return end + if self.contentWidget then + local selectedWidget = self.contentWidget:getLastChild() + if selectedWidget and selectedWidget.isTab then + self.contentWidget:removeChild(selectedWidget) + end + self.contentWidget:addChild(tab.tabPanel) + tab.tabPanel:fill('parent') + end + + if self.currentTab then + self.currentTab:setChecked(false) + end + signalcall(self.onTabChange, self, tab) + self.currentTab = tab + tab:setChecked(true) + tab:setOn(false) + tab.blinking = false + + if tab.blinkEvent then + removeEvent(tab.blinkEvent) + tab.blinkEvent = nil + end + + local parent = tab:getParent() + parent:focusChild(tab, MouseFocusReason) + updateNavigation(self) +end + +function UIMoveableTabBar:selectNextTab() + if self.currentTab == nil then + return + end + + local index = table.find(self.tabs, self.currentTab) + if index == nil then + return + end + + local newIndex = index + 1 + if newIndex > #self.tabs then + if #self.postTabs > 0 then + local widget = showPostTab(self) + self:selectTab(widget) + else + if #self.preTabs > 0 then + for i = 1, #self.preTabs do + showPreTab(self) + end + end + + self:selectTab(self.tabs[1]) + end + updateTabs(self) + return + end + + local nextTab = self.tabs[newIndex] + if not nextTab then + return + end + + self:selectTab(nextTab) +end + +function UIMoveableTabBar:selectPrevTab() + if self.currentTab == nil then + return + end + + local index = table.find(self.tabs, self.currentTab) + if index == nil then + return + end + + local newIndex = index - 1 + if newIndex <= 0 then + if #self.preTabs > 0 then + local widget = showPreTab(self) + self:selectTab(widget) + else + if #self.postTabs > 0 then + for i = 1, #self.postTabs do + showPostTab(self) + end + end + + self:selectTab(self.tabs[#self.tabs]) + end + updateTabs(self) + return + end + + local prevTab = self.tabs[newIndex] + if not prevTab then + return + end + + self:selectTab(prevTab) +end + +function UIMoveableTabBar:blinkTab(tab) + if tab:isChecked() then return end + tab.blinking = true + tabBlink(tab) +end + +function UIMoveableTabBar:getTabPanel(tab) + return tab.tabPanel +end + +function UIMoveableTabBar:getCurrentTabPanel() + if self.currentTab then + return self.currentTab.tabPanel + end +end + +function UIMoveableTabBar:getCurrentTab() + return self.currentTab +end + +function UIMoveableTabBar:setNavigation(prevButton, nextButton) + self.prevNavigation = prevButton + self.nextNavigation = nextButton + + if self.prevNavigation then + self.prevNavigation.onClick = function() self:selectPrevTab() end + end + if self.nextNavigation then + self.nextNavigation.onClick = function() self:selectNextTab() end + end + updateNavigation(self) +end diff --git a/800OTClient/modules/corelib/ui/uipopupmenu.lua b/800OTClient/modules/corelib/ui/uipopupmenu.lua new file mode 100644 index 0000000..16aaca0 --- /dev/null +++ b/800OTClient/modules/corelib/ui/uipopupmenu.lua @@ -0,0 +1,122 @@ +-- @docclass +UIPopupMenu = extends(UIWidget, "UIPopupMenu") + +local currentMenu + +function UIPopupMenu.create() + local menu = UIPopupMenu.internalCreate() + local layout = UIVerticalLayout.create(menu) + layout:setFitChildren(true) + menu:setLayout(layout) + menu.isGameMenu = false + return menu +end + +function UIPopupMenu:display(pos) + -- don't display if not options was added + if self:getChildCount() == 0 then + self:destroy() + return + end + + if g_ui.isMouseGrabbed() then + self:destroy() + return + end + + if currentMenu then + currentMenu:destroy() + end + + if pos == nil then + pos = g_window.getMousePosition() + end + + rootWidget:addChild(self) + self:setPosition(pos) + self:grabMouse() + self:focus() + --self:grabKeyboard() + currentMenu = self +end + +function UIPopupMenu:onGeometryChange(oldRect, newRect) + local parent = self:getParent() + if not parent then return end + local ymax = parent:getY() + parent:getHeight() + local xmax = parent:getX() + parent:getWidth() + if newRect.y + newRect.height > ymax then + local newy = ymax - newRect.height + if newy > 0 and newy + newRect.height < ymax then self:setY(newy) end + end + if newRect.x + newRect.width > xmax then + local newx = xmax - newRect.width + if newx > 0 and newx + newRect.width < xmax then self:setX(newx) end + end + self:bindRectToParent() +end + +function UIPopupMenu:addOption(optionName, optionCallback, shortcut) + local optionWidget = g_ui.createWidget(self:getStyleName() .. 'Button', self) + optionWidget.onClick = function(widget) + self:destroy() + optionCallback() + end + optionWidget:setText(optionName) + local width = optionWidget:getTextSize().width + optionWidget:getMarginLeft() + optionWidget:getMarginRight() + 15 + + if shortcut then + local shortcutLabel = g_ui.createWidget(self:getStyleName() .. 'ShortcutLabel', optionWidget) + shortcutLabel:setText(shortcut) + width = width + shortcutLabel:getTextSize().width + shortcutLabel:getMarginLeft() + shortcutLabel:getMarginRight() + end + + self:setWidth(math.max(self:getWidth(), width)) +end + +function UIPopupMenu:addSeparator() + g_ui.createWidget(self:getStyleName() .. 'Separator', self) +end + +function UIPopupMenu:setGameMenu(state) + self.isGameMenu = state +end + +function UIPopupMenu:onDestroy() + if currentMenu == self then + currentMenu = nil + end + self:ungrabMouse() +end + +function UIPopupMenu:onMousePress(mousePos, mouseButton) + -- clicks outside menu area destroys the menu + if not self:containsPoint(mousePos) then + self:destroy() + end + return true +end + +function UIPopupMenu:onKeyPress(keyCode, keyboardModifiers) + if keyCode == KeyEscape then + self:destroy() + return true + end + return false +end + +-- close all menus when the window is resized +local function onRootGeometryUpdate() + if currentMenu then + currentMenu:destroy() + end +end + +local function onGameEnd() + if currentMenu and currentMenu.isGameMenu then + currentMenu:destroy() + end +end + +connect(rootWidget, { onGeometryChange = onRootGeometryUpdate }) +connect(g_game, { onGameEnd = onGameEnd } ) diff --git a/800OTClient/modules/corelib/ui/uipopupscrollmenu.lua b/800OTClient/modules/corelib/ui/uipopupscrollmenu.lua new file mode 100644 index 0000000..405054f --- /dev/null +++ b/800OTClient/modules/corelib/ui/uipopupscrollmenu.lua @@ -0,0 +1,129 @@ +-- @docclass +UIPopupScrollMenu = extends(UIWidget, "UIPopupScrollMenu") + +local currentMenu + +function UIPopupScrollMenu.create() + local menu = UIPopupScrollMenu.internalCreate() + + local scrollArea = g_ui.createWidget('UIScrollArea', menu) + scrollArea:setLayout(UIVerticalLayout.create(menu)) + scrollArea:setId('scrollArea') + + local scrollBar = g_ui.createWidget('VerticalScrollBar', menu) + scrollBar:setId('scrollBar') + scrollBar.pixelsScroll = false + + scrollBar:addAnchor(AnchorRight, 'parent', AnchorRight) + scrollBar:addAnchor(AnchorTop, 'parent', AnchorTop) + scrollBar:addAnchor(AnchorBottom, 'parent', AnchorBottom) + + scrollArea:addAnchor(AnchorLeft, 'parent', AnchorLeft) + scrollArea:addAnchor(AnchorTop, 'parent', AnchorTop) + scrollArea:addAnchor(AnchorBottom, 'parent', AnchorBottom) + scrollArea:addAnchor(AnchorRight, 'next', AnchorLeft) + scrollArea:setVerticalScrollBar(scrollBar) + + menu.scrollArea = scrollArea + menu.scrollBar = scrollBar + return menu +end + +function UIPopupScrollMenu:setScrollbarStep(step) + self.scrollBar:setStep(step) +end + +function UIPopupScrollMenu:display(pos) + -- don't display if not options was added + if self.scrollArea:getChildCount() == 0 then + self:destroy() + return + end + + if g_ui.isMouseGrabbed() then + self:destroy() + return + end + + if currentMenu then + currentMenu:destroy() + end + + if pos == nil then + pos = g_window.getMousePosition() + end + + rootWidget:addChild(self) + self:setPosition(pos) + self:grabMouse() + currentMenu = self +end + +function UIPopupScrollMenu:onGeometryChange(oldRect, newRect) + local parent = self:getParent() + if not parent then return end + local ymax = parent:getY() + parent:getHeight() + local xmax = parent:getX() + parent:getWidth() + if newRect.y + newRect.height > ymax then + local newy = newRect.y - newRect.height + if newy > 0 and newy + newRect.height < ymax then self:setY(newy) end + end + if newRect.x + newRect.width > xmax then + local newx = newRect.x - newRect.width + if newx > 0 and newx + newRect.width < xmax then self:setX(newx) end + end + self:bindRectToParent() +end + +function UIPopupScrollMenu:addOption(optionName, optionCallback, shortcut) + local optionWidget = g_ui.createWidget(self:getStyleName() .. 'Button', self.scrollArea) + optionWidget.onClick = function(widget) + self:destroy() + optionCallback() + end + optionWidget:setText(optionName) + local width = optionWidget:getTextSize().width + optionWidget:getMarginLeft() + optionWidget:getMarginRight() + 15 + + if shortcut then + local shortcutLabel = g_ui.createWidget(self:getStyleName() .. 'ShortcutLabel', optionWidget) + shortcutLabel:setText(shortcut) + width = width + shortcutLabel:getTextSize().width + shortcutLabel:getMarginLeft() + shortcutLabel:getMarginRight() + end + + self:setWidth(math.max(self:getWidth(), width)) +end + +function UIPopupScrollMenu:addSeparator() + g_ui.createWidget(self:getStyleName() .. 'Separator', self.scrollArea) +end + +function UIPopupScrollMenu:onDestroy() + if currentMenu == self then + currentMenu = nil + end + self:ungrabMouse() +end + +function UIPopupScrollMenu:onMousePress(mousePos, mouseButton) + -- clicks outside menu area destroys the menu + if not self:containsPoint(mousePos) then + self:destroy() + end + return true +end + +function UIPopupScrollMenu:onKeyPress(keyCode, keyboardModifiers) + if keyCode == KeyEscape then + self:destroy() + return true + end + return false +end + +-- close all menus when the window is resized +local function onRootGeometryUpdate() + if currentMenu then + currentMenu:destroy() + end +end +connect(rootWidget, { onGeometryChange = onRootGeometryUpdate} ) diff --git a/800OTClient/modules/corelib/ui/uiprogressbar.lua b/800OTClient/modules/corelib/ui/uiprogressbar.lua new file mode 100644 index 0000000..35658a2 --- /dev/null +++ b/800OTClient/modules/corelib/ui/uiprogressbar.lua @@ -0,0 +1,99 @@ +-- @docclass +UIProgressBar = extends(UIWidget, "UIProgressBar") + +function UIProgressBar.create() + local progressbar = UIProgressBar.internalCreate() + progressbar:setFocusable(false) + progressbar:setOn(true) + progressbar.min = 0 + progressbar.max = 100 + progressbar.value = 0 + progressbar.bgBorderLeft = 0 + progressbar.bgBorderRight = 0 + progressbar.bgBorderTop = 0 + progressbar.bgBorderBottom = 0 + return progressbar +end + +function UIProgressBar:setMinimum(minimum) + self.minimum = minimum + if self.value < minimum then + self:setValue(minimum) + end +end + +function UIProgressBar:setMaximum(maximum) + self.maximum = maximum + if self.value > maximum then + self:setValue(maximum) + end +end + +function UIProgressBar:setValue(value, minimum, maximum) + if minimum then + self:setMinimum(minimum) + end + + if maximum then + self:setMaximum(maximum) + end + + self.value = math.max(math.min(value, self.maximum), self.minimum) + self:updateBackground() +end + +function UIProgressBar:setPercent(percent) + self:setValue(percent, 0, 100) +end + +function UIProgressBar:getPercent() + return self.value +end + +function UIProgressBar:getPercentPixels() + return (self.maximum - self.minimum) / self:getWidth() +end + +function UIProgressBar:getProgress() + if self.minimum == self.maximum then return 1 end + return (self.value - self.minimum) / (self.maximum - self.minimum) +end + +function UIProgressBar:updateBackground() + if self:isOn() then + local width = math.round(math.max((self:getProgress() * (self:getWidth() - self.bgBorderLeft - self.bgBorderRight)), 1)) + local height = self:getHeight() - self.bgBorderTop - self.bgBorderBottom + local rect = { x = self.bgBorderLeft, y = self.bgBorderTop, width = width, height = height } + self:setBackgroundRect(rect) + end +end + +function UIProgressBar:onSetup() + self:updateBackground() +end + +function UIProgressBar:onStyleApply(name, node) + for name,value in pairs(node) do + if name == 'background-border-left' then + self.bgBorderLeft = tonumber(value) + elseif name == 'background-border-right' then + self.bgBorderRight = tonumber(value) + elseif name == 'background-border-top' then + self.bgBorderTop = tonumber(value) + elseif name == 'background-border-bottom' then + self.bgBorderBottom = tonumber(value) + elseif name == 'background-border' then + self.bgBorderLeft = tonumber(value) + self.bgBorderRight = tonumber(value) + self.bgBorderTop = tonumber(value) + self.bgBorderBottom = tonumber(value) + end + end +end + +function UIProgressBar:onGeometryChange(oldRect, newRect) + if not self:isOn() then + self:setHeight(0) + end + self:updateBackground() +end diff --git a/800OTClient/modules/corelib/ui/uiradiogroup.lua b/800OTClient/modules/corelib/ui/uiradiogroup.lua new file mode 100644 index 0000000..682aece --- /dev/null +++ b/800OTClient/modules/corelib/ui/uiradiogroup.lua @@ -0,0 +1,66 @@ +-- @docclass +UIRadioGroup = newclass("UIRadioGroup") + +function UIRadioGroup.create() + local radiogroup = UIRadioGroup.internalCreate() + radiogroup.widgets = {} + radiogroup.selectedWidget = nil + return radiogroup +end + +function UIRadioGroup:destroy() + for k,widget in pairs(self.widgets) do + widget.onClick = nil + end + self.widgets = {} +end + +function UIRadioGroup:addWidget(widget) + table.insert(self.widgets, widget) + widget.onClick = function(widget) self:selectWidget(widget) end +end + +function UIRadioGroup:removeWidget(widget) + if self.selectedWidget == widget then + self:selectWidget(nil) + end + widget.onClick = nil + table.removevalue(self.widgets, widget) +end + +function UIRadioGroup:selectWidget(selectedWidget, dontSignal) + if selectedWidget == self.selectedWidget then return end + + local previousSelectedWidget = self.selectedWidget + self.selectedWidget = selectedWidget + + if previousSelectedWidget then + previousSelectedWidget:setChecked(false) + end + + if selectedWidget then + selectedWidget:setChecked(true) + end + + if not dontSignal then + signalcall(self.onSelectionChange, self, selectedWidget, previousSelectedWidget) + end +end + +function UIRadioGroup:clearSelected() + if not self.selectedWidget then return end + + local previousSelectedWidget = self.selectedWidget + self.selectedWidget:setChecked(false) + self.selectedWidget = nil + + signalcall(self.onSelectionChange, self, nil, previousSelectedWidget) +end + +function UIRadioGroup:getSelectedWidget() + return self.selectedWidget +end + +function UIRadioGroup:getFirstWidget() + return self.widgets[1] +end diff --git a/800OTClient/modules/corelib/ui/uiresizeborder.lua b/800OTClient/modules/corelib/ui/uiresizeborder.lua new file mode 100644 index 0000000..4a86ce3 --- /dev/null +++ b/800OTClient/modules/corelib/ui/uiresizeborder.lua @@ -0,0 +1,132 @@ +-- @docclass +UIResizeBorder = extends(UIWidget, "UIResizeBorder") + +function UIResizeBorder.create() + local resizeborder = UIResizeBorder.internalCreate() + resizeborder:setFocusable(false) + resizeborder.minimum = 0 + resizeborder.maximum = 1000 + return resizeborder +end + +function UIResizeBorder:onSetup() + if self:getWidth() > self:getHeight() then + self.vertical = true + else + self.vertical = false + end +end + +function UIResizeBorder:onDestroy() + if self.hovering then + g_mouse.popCursor(self.cursortype) + end +end + +function UIResizeBorder:onHoverChange(hovered) + if hovered then + if g_mouse.isCursorChanged() or g_mouse.isPressed() then return end + if self:getWidth() > self:getHeight() then + self.vertical = true + self.cursortype = 'vertical' + else + self.vertical = false + self.cursortype = 'horizontal' + end + g_mouse.pushCursor(self.cursortype) + self.hovering = true + if not self:isPressed() then + g_effects.fadeIn(self) + end + else + if not self:isPressed() and self.hovering then + g_mouse.popCursor(self.cursortype) + g_effects.fadeOut(self) + self.hovering = false + end + end +end + +function UIResizeBorder:onMouseMove(mousePos, mouseMoved) + if self:isPressed() then + local parent = self:getParent() + local newSize = 0 + if self.vertical then + local delta = mousePos.y - self:getY() - self:getHeight()/2 + newSize = math.min(math.max(parent:getHeight() + delta, self.minimum), self.maximum) + parent:setHeight(newSize) + else + local delta = mousePos.x - self:getX() - self:getWidth()/2 + newSize = math.min(math.max(parent:getWidth() + delta, self.minimum), self.maximum) + parent:setWidth(newSize) + end + + self:checkBoundary(newSize) + return true + end +end + +function UIResizeBorder:onMouseRelease(mousePos, mouseButton) + if not self:isHovered() then + g_mouse.popCursor(self.cursortype) + g_effects.fadeOut(self) + self.hovering = false + end +end + +function UIResizeBorder:onStyleApply(styleName, styleNode) + for name,value in pairs(styleNode) do + if name == 'maximum' then + self:setMaximum(tonumber(value)) + elseif name == 'minimum' then + self:setMinimum(tonumber(value)) + end + end +end + +function UIResizeBorder:onVisibilityChange(visible) + if visible and self.maximum == self.minimum then + self:hide() + end +end + +function UIResizeBorder:setMaximum(maximum) + self.maximum = maximum + self:checkBoundary() +end + +function UIResizeBorder:setMinimum(minimum) + self.minimum = minimum + self:checkBoundary() +end + +function UIResizeBorder:getMaximum() return self.maximum end +function UIResizeBorder:getMinimum() return self.minimum end + +function UIResizeBorder:setParentSize(size) + local parent = self:getParent() + if self.vertical then + parent:setHeight(size) + else + parent:setWidth(size) + end + self:checkBoundary(size) +end + +function UIResizeBorder:getParentSize() + local parent = self:getParent() + if self.vertical then + return parent:getHeight() + else + return parent:getWidth() + end +end + +function UIResizeBorder:checkBoundary(size) + size = size or self:getParentSize() + if self.maximum == self.minimum and size == self.maximum then + self:hide() + else + self:show() + end +end diff --git a/800OTClient/modules/corelib/ui/uiscrollarea.lua b/800OTClient/modules/corelib/ui/uiscrollarea.lua new file mode 100644 index 0000000..5f274ec --- /dev/null +++ b/800OTClient/modules/corelib/ui/uiscrollarea.lua @@ -0,0 +1,190 @@ +-- @docclass +UIScrollArea = extends(UIWidget, "UIScrollArea") + +-- public functions +function UIScrollArea.create() + local scrollarea = UIScrollArea.internalCreate() + scrollarea:setClipping(true) + scrollarea.inverted = false + scrollarea.alwaysScrollMaximum = false + return scrollarea +end + +function UIScrollArea:onStyleApply(styleName, styleNode) + for name,value in pairs(styleNode) do + if name == 'vertical-scrollbar' then + addEvent(function() + local parent = self:getParent() + if parent then + self:setVerticalScrollBar(parent:getChildById(value)) + end + end) + elseif name == 'horizontal-scrollbar' then + addEvent(function() + local parent = self:getParent() + if parent then + self:setHorizontalScrollBar(self:getParent():getChildById(value)) + end + end) + elseif name == 'inverted-scroll' then + self:setInverted(value) + elseif name == 'always-scroll-maximum' then + self:setAlwaysScrollMaximum(value) + end + end +end + +function UIScrollArea:updateScrollBars() + local scrollWidth = math.max(self:getChildrenRect().width - self:getPaddingRect().width, 0) + local scrollHeight = math.max(self:getChildrenRect().height - self:getPaddingRect().height, 0) + + local scrollbar = self.verticalScrollBar + if scrollbar then + if self.inverted then + scrollbar:setMinimum(-scrollHeight) + scrollbar:setMaximum(0) + else + scrollbar:setMinimum(0) + scrollbar:setMaximum(scrollHeight) + end + end + + local scrollbar = self.horizontalScrollBar + if scrollbar then + if self.inverted then + scrollbar:setMinimum(-scrollWidth) + scrollbar:setMaximum(0) + else + scrollbar:setMinimum(0) + scrollbar:setMaximum(scrollWidth) + end + end + + if self.lastScrollWidth ~= scrollWidth then + self:onScrollWidthChange() + end + if self.lastScrollHeight ~= scrollHeight then + self:onScrollHeightChange() + end + + self.lastScrollWidth = scrollWidth + self.lastScrollHeight = scrollHeight +end + +function UIScrollArea:setVerticalScrollBar(scrollbar) + self.verticalScrollBar = scrollbar + connect(self.verticalScrollBar, 'onValueChange', function(scrollbar, value) + local virtualOffset = self:getVirtualOffset() + virtualOffset.y = value + self:setVirtualOffset(virtualOffset) + signalcall(self.onScrollChange, self, virtualOffset) + end) + self:updateScrollBars() +end + +function UIScrollArea:setHorizontalScrollBar(scrollbar) + self.horizontalScrollBar = scrollbar + connect(self.horizontalScrollBar, 'onValueChange', function(scrollbar, value) + local virtualOffset = self:getVirtualOffset() + virtualOffset.x = value + self:setVirtualOffset(virtualOffset) + signalcall(self.onScrollChange, self, virtualOffset) + end) + self:updateScrollBars() +end + +function UIScrollArea:setInverted(inverted) + self.inverted = inverted +end + +function UIScrollArea:setAlwaysScrollMaximum(value) + self.alwaysScrollMaximum = value +end + +function UIScrollArea:onLayoutUpdate() + self:updateScrollBars() +end + +function UIScrollArea:onMouseWheel(mousePos, mouseWheel) + if self.verticalScrollBar then + if not self.verticalScrollBar:isOn() then + return false + end + if mouseWheel == MouseWheelUp then + local minimum = self.verticalScrollBar:getMinimum() + if self.verticalScrollBar:getValue() <= minimum then + return false + end + self.verticalScrollBar:decrement() + else + local maximum = self.verticalScrollBar:getMaximum() + if self.verticalScrollBar:getValue() >= maximum then + return false + end + self.verticalScrollBar:increment() + end + elseif self.horizontalScrollBar then + if not self.horizontalScrollBar:isOn() then + return false + end + if mouseWheel == MouseWheelUp then + local maximum = self.horizontalScrollBar:getMaximum() + if self.horizontalScrollBar:getValue() >= maximum then + return false + end + self.horizontalScrollBar:increment() + else + local minimum = self.horizontalScrollBar:getMinimum() + if self.horizontalScrollBar:getValue() <= minimum then + return false + end + self.horizontalScrollBar:decrement() + end + end + return true +end + +function UIScrollArea:ensureChildVisible(child) + if child then + local paddingRect = self:getPaddingRect() + if self.verticalScrollBar then + local deltaY = paddingRect.y - child:getY() + if deltaY > 0 then + self.verticalScrollBar:decrement(deltaY) + end + + deltaY = (child:getY() + child:getHeight()) - (paddingRect.y + paddingRect.height) + if deltaY > 0 then + self.verticalScrollBar:increment(deltaY) + end + elseif self.horizontalScrollBar then + local deltaX = paddingRect.x - child:getX() + if deltaX > 0 then + self.horizontalScrollBar:decrement(deltaX) + end + + deltaX = (child:getX() + child:getWidth()) - (paddingRect.x + paddingRect.width) + if deltaX > 0 then + self.horizontalScrollBar:increment(deltaX) + end + end + end +end + +function UIScrollArea:onChildFocusChange(focusedChild, oldFocused, reason) + if focusedChild and (reason == MouseFocusReason or reason == KeyboardFocusReason) then + self:ensureChildVisible(focusedChild) + end +end + +function UIScrollArea:onScrollWidthChange() + if self.alwaysScrollMaximum and self.horizontalScrollBar then + self.horizontalScrollBar:setValue(self.horizontalScrollBar:getMaximum()) + end +end + +function UIScrollArea:onScrollHeightChange() + if self.alwaysScrollMaximum and self.verticalScrollBar then + self.verticalScrollBar:setValue(self.verticalScrollBar:getMaximum()) + end +end diff --git a/800OTClient/modules/corelib/ui/uiscrollbar.lua b/800OTClient/modules/corelib/ui/uiscrollbar.lua new file mode 100644 index 0000000..940d3eb --- /dev/null +++ b/800OTClient/modules/corelib/ui/uiscrollbar.lua @@ -0,0 +1,290 @@ +-- @docclass +UIScrollBar = extends(UIWidget, "UIScrollBar") + +-- private functions +local function calcValues(self) + local slider = self:getChildById('sliderButton') + local decrementButton = self:getChildById('decrementButton') + local incrementButton = self:getChildById('incrementButton') + + local pxrange, center + if self.orientation == 'vertical' then + pxrange = (self:getHeight() - decrementButton:getHeight() - decrementButton:getMarginTop() - decrementButton:getMarginBottom() + - incrementButton:getHeight() - incrementButton:getMarginTop() - incrementButton:getMarginBottom()) + center = self:getY() + math.floor(self:getHeight() / 2) + else -- horizontal + pxrange = (self:getWidth() - decrementButton:getWidth() - decrementButton:getMarginLeft() - decrementButton:getMarginRight() + - incrementButton:getWidth() - incrementButton:getMarginLeft() - incrementButton:getMarginRight()) + center = self:getX() + math.floor(self:getWidth() / 2) + end + + local range = self.maximum - self.minimum + 1 + + local proportion + + if self.pixelsScroll then + proportion = pxrange/(range+pxrange) + else + proportion = math.min(math.max(self.step, 1), range)/range + end + + local px = math.max(proportion * pxrange, 6) + if g_app.isMobile() then + px = math.max(proportion * pxrange, 24) + end + px = px - px % 2 + 1 + + local offset = 0 + if range == 0 or self.value == self.minimum then + if self.orientation == 'vertical' then + offset = -math.floor((self:getHeight() - px) / 2) + decrementButton:getMarginRect().height + else + offset = -math.floor((self:getWidth() - px) / 2) + decrementButton:getMarginRect().width + end + elseif range > 1 and self.value == self.maximum then + if self.orientation == 'vertical' then + offset = math.ceil((self:getHeight() - px) / 2) - incrementButton:getMarginRect().height + else + offset = math.ceil((self:getWidth() - px) / 2) - incrementButton:getMarginRect().width + end + elseif range > 1 then + offset = (((self.value - self.minimum) / (range - 1)) - 0.5) * (pxrange - px) + end + + return range, pxrange, px, offset, center +end + +local function updateValueDisplay(widget) + if widget == nil then return end + + if widget:getShowValue() then + widget:setText(widget:getValue() .. (widget:getSymbol() or '')) + end +end + +local function updateSlider(self) + local slider = self:getChildById('sliderButton') + if slider == nil then return end + + local range, pxrange, px, offset, center = calcValues(self) + if self.orientation == 'vertical' then + slider:setHeight(px) + slider:setMarginTop(offset) + else -- horizontal + slider:setWidth(px) + slider:setMarginLeft(offset) + end + updateValueDisplay(self) + + local status = (self.maximum ~= self.minimum) + + self:setOn(status) + for _i,child in pairs(self:getChildren()) do + child:setEnabled(status) + end +end + +local function parseSliderPos(self, slider, pos, move) + local delta, hotDistance + if self.orientation == 'vertical' then + delta = move.y + hotDistance = pos.y - slider:getY() + else + delta = move.x + hotDistance = pos.x - slider:getX() + end + + if (delta > 0 and hotDistance + delta > self.hotDistance) or + (delta < 0 and hotDistance + delta < self.hotDistance) then + local range, pxrange, px, offset, center = calcValues(self) + local newvalue = self.value + delta * (range / (pxrange - px)) + self:setValue(newvalue) + end +end + +local function parseSliderPress(self, slider, pos, button) + if self.orientation == 'vertical' then + self.hotDistance = pos.y - slider:getY() + else + self.hotDistance = pos.x - slider:getX() + end +end + +-- public functions +function UIScrollBar.create() + local scrollbar = UIScrollBar.internalCreate() + scrollbar:setFocusable(false) + scrollbar.value = 0 + scrollbar.minimum = -999999 + scrollbar.maximum = 999999 + scrollbar.step = 1 + scrollbar.orientation = 'vertical' + scrollbar.pixelsScroll = false + scrollbar.showValue = false + scrollbar.symbol = nil + scrollbar.mouseScroll = true + return scrollbar +end + +function UIScrollBar:onSetup() + self.setupDone = true + local sliderButton = self:getChildById('sliderButton') + g_mouse.bindAutoPress(self:getChildById('decrementButton'), function() self:onDecrement() end, 300) + g_mouse.bindAutoPress(self:getChildById('incrementButton'), function() self:onIncrement() end, 300) + g_mouse.bindPressMove(sliderButton, function(mousePos, mouseMoved) parseSliderPos(self, sliderButton, mousePos, mouseMoved) end) + g_mouse.bindPress(sliderButton, function(mousePos, mouseButton) parseSliderPress(self, sliderButton, mousePos, mouseButton) end) + + updateSlider(self) +end + +function UIScrollBar:onStyleApply(styleName, styleNode) + for name,value in pairs(styleNode) do + if name == 'maximum' then + self:setMaximum(tonumber(value)) + elseif name == 'minimum' then + self:setMinimum(tonumber(value)) + elseif name == 'step' then + self:setStep(tonumber(value)) + elseif name == 'orientation' then + self:setOrientation(value) + elseif name == 'value' then + self:setValue(value) + elseif name == 'pixels-scroll' then + self.pixelsScroll = true + elseif name == 'show-value' then + self.showValue = true + elseif name == 'symbol' then + self.symbol = value + elseif name == 'mouse-scroll' then + self.mouseScroll = value + end + end +end + +function UIScrollBar:onDecrement() + if g_keyboard.isCtrlPressed() then + self:decrement(self.value) + elseif g_keyboard.isShiftPressed() then + self:decrement(10) + else + self:decrement() + end +end + +function UIScrollBar:onIncrement() + if g_keyboard.isCtrlPressed() then + self:increment(self.maximum) + elseif g_keyboard.isShiftPressed() then + self:increment(10) + else + self:increment() + end +end + +function UIScrollBar:decrement(count) + count = count or self.step + self:setValue(self.value - count) +end + +function UIScrollBar:increment(count) + count = count or self.step + self:setValue(self.value + count) +end + +function UIScrollBar:setMaximum(maximum) + if maximum == self.maximum then return end + self.maximum = maximum + if self.minimum > maximum then + self:setMinimum(maximum) + end + if self.value > maximum then + self:setValue(maximum) + else + updateSlider(self) + end +end + +function UIScrollBar:setMinimum(minimum) + if minimum == self.minimum then return end + self.minimum = minimum + if self.maximum < minimum then + self:setMaximum(minimum) + end + if self.value < minimum then + self:setValue(minimum) + else + updateSlider(self) + end +end + +function UIScrollBar:setRange(minimum, maximum) + self:setMinimum(minimum) + self:setMaximum(maximum) +end + +function UIScrollBar:setValue(value) + value = math.max(math.min(value, self.maximum), self.minimum) + if self.value == value then return end + local delta = value - self.value + self.value = value + updateSlider(self) + if self.setupDone then + signalcall(self.onValueChange, self, math.round(value), delta) + end +end + +function UIScrollBar:setMouseScroll(scroll) + self.mouseScroll = scroll +end + +function UIScrollBar:setStep(step) + self.step = step +end + +function UIScrollBar:setOrientation(orientation) + self.orientation = orientation +end + +function UIScrollBar:setText(text) + local valueLabel = self:getChildById('valueLabel') + if valueLabel then + valueLabel:setText(text) + end +end + +function UIScrollBar:onGeometryChange() + updateSlider(self) +end + +function UIScrollBar:onMouseWheel(mousePos, mouseWheel) + if not self.mouseScroll or not self:isOn() or self.disableScroll then + return false + end + if mouseWheel == MouseWheelUp then + if self.orientation == 'vertical' then + if self.value <= self.minimum then return false end + self:decrement() + else + if self.value >= self.maximum then return false end + self:increment() + end + else + if self.orientation == 'vertical' then + if self.value >= self.maximum then return false end + self:increment() + else + if self.value <= self.minimum then return false end + self:decrement() + end + end + return true +end + +function UIScrollBar:getMaximum() return self.maximum end +function UIScrollBar:getMinimum() return self.minimum end +function UIScrollBar:getValue() return math.round(self.value) end +function UIScrollBar:getStep() return self.step end +function UIScrollBar:getOrientation() return self.orientation end +function UIScrollBar:getShowValue() return self.showValue end +function UIScrollBar:getSymbol() return self.symbol end +function UIScrollBar:getMouseScroll() return self.mouseScroll end diff --git a/800OTClient/modules/corelib/ui/uispinbox.lua b/800OTClient/modules/corelib/ui/uispinbox.lua new file mode 100644 index 0000000..a644db4 --- /dev/null +++ b/800OTClient/modules/corelib/ui/uispinbox.lua @@ -0,0 +1,191 @@ +-- @docclass +UISpinBox = extends(UITextEdit, "UISpinBox") + +function UISpinBox.create() + local spinbox = UISpinBox.internalCreate() + spinbox:setFocusable(false) + spinbox:setValidCharacters('0123456789') + spinbox.displayButtons = true + spinbox.minimum = 0 + spinbox.maximum = 1 + spinbox.value = 0 + spinbox.step = 1 + spinbox.firstchange = true + spinbox.mouseScroll = true + spinbox:setText("1") + spinbox:setValue(1) + return spinbox +end + +function UISpinBox:onSetup() + g_mouse.bindAutoPress(self:getChildById('up'), function() self:up() end, 300) + g_mouse.bindAutoPress(self:getChildById('down'), function() self:down() end, 300) +end + +function UISpinBox:onMouseWheel(mousePos, direction) + if not self.mouseScroll or self.disableScroll then + return false + end + if direction == MouseWheelUp then + self:up() + elseif direction == MouseWheelDown then + self:down() + end + return true +end + +function UISpinBox:onKeyPress() + if self.firstchange then + self.firstchange = false + self:setText('') + end + return false +end + +function UISpinBox:onTextChange(text, oldText) + if text:len() == 0 then + self:setValue(self.minimum) + return + end + + local number = tonumber(text) + if not number then + self:setText(number) + return + else + if number < self.minimum then + self:setText(self.minimum) + return + elseif number > self.maximum then + self:setText(self.maximum) + return + end + end + + self:setValue(number) +end + +function UISpinBox:onValueChange(value) + -- nothing to do +end + +function UISpinBox:onFocusChange(focused) + if not focused then + if self:getText():len() == 0 then + self:setText(self.minimum) + end + end +end + +function UISpinBox:onStyleApply(styleName, styleNode) + for name, value in pairs(styleNode) do + if name == 'maximum' then + self.maximum = value + addEvent(function() self:setMaximum(value) end) + elseif name == 'minimum' then + self.minimum = value + addEvent(function() self:setMinimum(value) end) + elseif name == 'mouse-scroll' then + addEvent(function() self:setMouseScroll(value) end) + elseif name == 'buttons' then + addEvent(function() + if value then + self:showButtons() + else + self:hideButtons() + end + end) + end + end +end + +function UISpinBox:showButtons() + self:getChildById('up'):show() + self:getChildById('down'):show() + self.displayButtons = true +end + +function UISpinBox:hideButtons() + self:getChildById('up'):hide() + self:getChildById('down'):hide() + self.displayButtons = false +end + +function UISpinBox:up() + self:setValue(self.value + self.step) +end + +function UISpinBox:down() + self:setValue(self.value - self.step) +end + +function UISpinBox:setValue(value, dontSignal) + if type(value) == "string" then + value = tonumber(value) + end + value = value or 0 + value = math.max(math.min(self.maximum, value), self.minimum) + + if value == self.value then return end + + self.value = value + if self:getText():len() > 0 then + self:setText(value) + end + + local upButton = self:getChildById('up') + local downButton = self:getChildById('down') + if upButton then + upButton:setEnabled(self.maximum ~= self.minimum and self.value ~= self.maximum) + end + if downButton then + downButton:setEnabled(self.maximum ~= self.minimum and self.value ~= self.minimum) + end + + if not dontSignal then + signalcall(self.onValueChange, self, value) + end +end + +function UISpinBox:getValue() + return self.value +end + +function UISpinBox:setMinimum(minimum) + minimum = minimum or -9223372036854775808 + self.minimum = minimum + if self.minimum > self.maximum then + self.maximum = self.minimum + end + if self.value < minimum then + self:setValue(minimum) + end +end + +function UISpinBox:getMinimum() + return self.minimum +end + +function UISpinBox:setMaximum(maximum) + maximum = maximum or 9223372036854775807 + self.maximum = maximum + if self.value > maximum then + self:setValue(maximum) + end +end + +function UISpinBox:getMaximum() + return self.maximum +end + +function UISpinBox:setStep(step) + self.step = step or 1 +end + +function UISpinBox:setMouseScroll(mouseScroll) + self.mouseScroll = mouseScroll +end + +function UISpinBox:getMouseScroll() + return self.mouseScroll +end \ No newline at end of file diff --git a/800OTClient/modules/corelib/ui/uisplitter.lua b/800OTClient/modules/corelib/ui/uisplitter.lua new file mode 100644 index 0000000..d8fb2e2 --- /dev/null +++ b/800OTClient/modules/corelib/ui/uisplitter.lua @@ -0,0 +1,85 @@ +-- @docclass +UISplitter = extends(UIWidget, "UISplitter") + +function UISplitter.create() + local splitter = UISplitter.internalCreate() + splitter:setFocusable(false) + splitter.relativeMargin = 'bottom' + return splitter +end + +function UISplitter:onHoverChange(hovered) + -- Check if margin can be changed + local margin = (self.vertical and self:getMarginBottom() or self:getMarginRight()) + if hovered and (self:canUpdateMargin(margin + 1) ~= margin or self:canUpdateMargin(margin - 1) ~= margin) then + if g_mouse.isCursorChanged() or g_mouse.isPressed() then return end + if self:getWidth() > self:getHeight() then + self.vertical = true + self.cursortype = 'vertical' + else + self.vertical = false + self.cursortype = 'horizontal' + end + self.hovering = true + g_mouse.pushCursor(self.cursortype) + if not self:isPressed() then + g_effects.fadeIn(self) + end + else + if not self:isPressed() and self.hovering then + g_mouse.popCursor(self.cursortype) + g_effects.fadeOut(self) + self.hovering = false + end + end +end + +function UISplitter:onMouseMove(mousePos, mouseMoved) + if self:isPressed() then + --local currentmargin, newmargin, delta + if self.vertical then + local delta = mousePos.y - self:getY() - self:getHeight()/2 + local newMargin = self:canUpdateMargin(self:getMarginBottom() - delta) + local currentMargin = self:getMarginBottom() + if newMargin ~= currentMargin then + self.newMargin = newMargin + if not self.event or self.event:isExecuted() then + self.event = addEvent(function() + self:setMarginBottom(self.newMargin) + end) + end + end + else + local delta = mousePos.x - self:getX() - self:getWidth()/2 + local newMargin = self:canUpdateMargin(self:getMarginRight() - delta) + local currentMargin = self:getMarginRight() + if newMargin ~= currentMargin then + self.newMargin = newMargin + if not self.event or self.event:isExecuted() then + self.event = addEvent(function() + self:setMarginRight(self.newMargin) + end) + end + end + end + return true + end +end + +function UISplitter:onMouseRelease(mousePos, mouseButton) + if not self:isHovered() then + g_mouse.popCursor(self.cursortype) + g_effects.fadeOut(self) + self.hovering = false + end +end + +function UISplitter:onStyleApply(styleName, styleNode) + if styleNode['relative-margin'] then + self.relativeMargin = styleNode['relative-margin'] + end +end + +function UISplitter:canUpdateMargin(newMargin) + return newMargin +end diff --git a/800OTClient/modules/corelib/ui/uitabbar.lua b/800OTClient/modules/corelib/ui/uitabbar.lua new file mode 100644 index 0000000..9840c53 --- /dev/null +++ b/800OTClient/modules/corelib/ui/uitabbar.lua @@ -0,0 +1,163 @@ +-- @docclass +UITabBar = extends(UIWidget, "UITabBar") + +-- private functions +local function onTabClick(tab) + tab.tabBar:selectTab(tab) +end + +local function onTabMouseRelease(tab, mousePos, mouseButton) + if mouseButton == MouseRightButton and tab:containsPoint(mousePos) then + signalcall(tab.tabBar.onTabLeftClick, tab.tabBar, tab) + end +end + +-- public functions +function UITabBar.create() + local tabbar = UITabBar.internalCreate() + tabbar:setFocusable(false) + tabbar.tabs = {} + return tabbar +end + +function UITabBar:onSetup() + self.buttonsPanel = self:getChildById('buttonsPanel') +end + +function UITabBar:setContentWidget(widget) + self.contentWidget = widget + if #self.tabs > 0 then + self.contentWidget:addChild(self.tabs[1].tabPanel) + end +end + +function UITabBar:addTab(text, panel, icon) + if panel == nil then + panel = g_ui.createWidget(self:getStyleName() .. 'Panel') + panel:setId('tabPanel') + end + + local tab = g_ui.createWidget(self:getStyleName() .. 'Button', self.buttonsPanel) + + panel.isTab = true + tab.tabPanel = panel + tab.tabBar = self + tab:setId('tab') + tab:setText(text) + tab:setWidth(tab:getTextSize().width + tab:getPaddingLeft() + tab:getPaddingRight()) + tab.onClick = onTabClick + tab.onMouseRelease = onTabMouseRelease + tab.onDestroy = function() tab.tabPanel:destroy() end + + table.insert(self.tabs, tab) + if #self.tabs == 1 then + self:selectTab(tab) + end + + local tabStyle = {} + tabStyle['icon-source'] = icon + tab:mergeStyle(tabStyle) + + return tab +end + +function UITabBar:addButton(text, func, icon) + local button = g_ui.createWidget(self:getStyleName() .. 'Button', self.buttonsPanel) + button:setText(text) + + local style = {} + style['icon-source'] = icon + button:mergeStyle(style) + + button.onClick = func + return button +end + +function UITabBar:removeTab(tab) + local index = table.find(self.tabs, tab) + if index == nil then return end + if self.currentTab == tab then + self:selectPrevTab() + end + table.remove(self.tabs, index) + tab:destroy() +end + +function UITabBar:getTab(text) + for k,tab in pairs(self.tabs) do + if tab:getText():lower() == text:lower() then + return tab + end + end +end + +function UITabBar:selectTab(tab) + if self.currentTab == tab then return end + if self.contentWidget then + local selectedWidget = self.contentWidget:getLastChild() + if selectedWidget and selectedWidget.isTab then + self.contentWidget:removeChild(selectedWidget) + end + self.contentWidget:addChild(tab.tabPanel) + tab.tabPanel:fill('parent') + end + + if self.currentTab then + self.currentTab:setChecked(false) + end + signalcall(self.onTabChange, self, tab) + self.currentTab = tab + tab:setChecked(true) + tab:setOn(false) + + local parent = tab:getParent() + if parent then + parent:focusChild(tab, MouseFocusReason) + end +end + +function UITabBar:selectNextTab() + if self.currentTab == nil then return end + local index = table.find(self.tabs, self.currentTab) + if index == nil then return end + local nextTab = self.tabs[index + 1] or self.tabs[1] + if not nextTab then return end + self:selectTab(nextTab) +end + +function UITabBar:selectPrevTab() + if self.currentTab == nil then return end + local index = table.find(self.tabs, self.currentTab) + if index == nil then return end + local prevTab = self.tabs[index - 1] or self.tabs[#self.tabs] + if not prevTab then return end + self:selectTab(prevTab) +end + +function UITabBar:getTabPanel(tab) + return tab.tabPanel +end + +function UITabBar:getCurrentTabPanel() + if self.currentTab then + return self.currentTab.tabPanel + end +end + +function UITabBar:getCurrentTab() + return self.currentTab +end + +function UITabBar:getTabs() + return self.tabs +end + +function UITabBar:getTabsPanel() + return table.collect(self.tabs, function(_,tab) return tab.tabPanel end) +end + +function UITabBar:clearTabs() + while #self.tabs > 0 do + self:removeTab(self.tabs[#self.tabs]) + end +end diff --git a/800OTClient/modules/corelib/ui/uitable.lua b/800OTClient/modules/corelib/ui/uitable.lua new file mode 100644 index 0000000..fb644e7 --- /dev/null +++ b/800OTClient/modules/corelib/ui/uitable.lua @@ -0,0 +1,432 @@ +-- @docclass +--[[ + TODO: + * Make table headers more robust. + * Get dynamic row heights working with text wrapping. +]] + +TABLE_SORTING_ASC = 0 +TABLE_SORTING_DESC = 1 + +UITable = extends(UIWidget, "UITable") + +-- Initialize default values +function UITable.create() + local table = UITable.internalCreate() + table.headerRow = nil + table.headerColumns = {} + table.dataSpace = nil + table.rows = {} + table.rowBaseStyle = nil + table.columns = {} + table.columnWidth = {} + table.columBaseStyle = nil + table.headerRowBaseStyle = nil + table.headerColumnBaseStyle = nil + table.selectedRow = nil + table.defaultColumnWidth = 80 + table.sortColumn = -1 + table.sortType = TABLE_SORTING_ASC + table.autoSort = false + + return table +end + +-- Clear table values +function UITable:onDestroy() + for _,row in pairs(self.rows) do + row.onClick = nil + end + self.rows = {} + self.columns = {} + self.headerRow = nil + self.headerColumns = {} + self.columnWidth = {} + self.selectedRow = nil + + if self.dataSpace then + self.dataSpace:destroyChildren() + self.dataSpace = nil + end +end + +-- Detect if a header is already defined +function UITable:onSetup() + local header = self:getChildById('header') + if header then + self:setHeader(header) + end +end + +-- Parse table related styles +function UITable:onStyleApply(styleName, styleNode) + for name, value in pairs(styleNode) do + if value ~= false then + if name == 'table-data' then + addEvent(function() + self:setTableData(self:getParent():getChildById(value)) + end) + elseif name == 'column-style' then + addEvent(function() + self:setColumnStyle(value) + end) + elseif name == 'row-style' then + addEvent(function() + self:setRowStyle(value) + end) + elseif name == 'header-column-style' then + addEvent(function() + self:setHeaderColumnStyle(value) + end) + elseif name == 'header-row-style' then + addEvent(function() + self:setHeaderRowStyle(value) + end) + end + end + end +end + +function UITable:setColumnWidth(width) + if self:hasHeader() then return end + self.columnWidth = width +end + +function UITable:setDefaultColumnWidth(width) + self.defaultColumnWidth = width +end + +-- Check if the table has a header +function UITable:hasHeader() + return self.headerRow ~= nil +end + +-- Clear all rows +function UITable:clearData() + if not self.dataSpace then + return + end + self.dataSpace:destroyChildren() + self.selectedRow = nil + self.columns = {} + self.rows = {} +end + +-- Set existing child as header +function UITable:setHeader(headerWidget) + self:removeHeader() + + if self.dataSpace then + local newHeight = self.dataSpace:getHeight()-headerRow:getHeight()-self.dataSpace:getMarginTop() + self.dataSpace:applyStyle({ height = newHeight }) + end + + self.headerColumns = {} + self.columnWidth = {} + for colId, column in pairs(headerWidget:getChildren()) do + column.colId = colId + column.table = self + table.insert(self.columnWidth, column:getWidth()) + table.insert(self.headerColumns, column) + end + + self.headerRow = headerWidget +end + +-- Create and add header from table data +function UITable:addHeader(data) + if not data or type(data) ~= 'table' then + g_logger.error('UITable:addHeaderRow - table columns must be provided in a table') + return + end + + self:removeHeader() + + -- build header columns + local columns = {} + for colId, column in pairs(data) do + local col = g_ui.createWidget(self.headerColumnBaseStyle) + col.colId = colId + col.table = self + for type, value in pairs(column) do + if type == 'width' then + col:setWidth(value) + elseif type == 'height' then + col:setHeight(value) + elseif type == 'text' then + col:setText(value) + elseif type == 'onClick' then + col.onClick = value + end + end + table.insert(columns, col) + end + + -- create a new header + local headerRow = g_ui.createWidget(self.headerRowBaseStyle, self) + local newHeight = self.dataSpace:getHeight()-headerRow:getHeight()-self.dataSpace:getMarginTop() + self.dataSpace:applyStyle({ height = newHeight }) + + headerRow:setId('header') + self.headerColumns = {} + self.columnWidth = {} + for _, column in pairs(columns) do + headerRow:addChild(column) + table.insert(self.columnWidth, column:getWidth()) + table.insert(self.headerColumns, column) + end + + self.headerRow = headerRow + return headerRow +end + +-- Remove header +function UITable:removeHeader() + if self:hasHeader() then + if self.dataSpace then + local newHeight = self.dataSpace:getHeight()+self.headerRow:getHeight()+self.dataSpace:getMarginTop() + self.dataSpace:applyStyle({ height = newHeight }) + end + self.headerColumns = {} + self.columnWidth = {} + self.headerRow:destroy() + self.headerRow = nil + end +end + +function UITable:addRow(data, height) + if not self.dataSpace then + g_logger.error('UITable:addRow - table data space has not been set, cannot add rows.') + return + end + if not data or type(data) ~= 'table' then + g_logger.error('UITable:addRow - table columns must be provided in a table.') + return + end + + local row = g_ui.createWidget(self.rowBaseStyle) + row.table = self + if height then row:setHeight(height) end + + local rowId = #self.rows + 1 + row.rowId = rowId + row:setId('row'..rowId) + row:updateBackgroundColor() + + self.columns[rowId] = {} + for colId, column in pairs(data) do + local col = g_ui.createWidget(self.columBaseStyle, row) + if column.width then + col:setWidth(column.width) + else + col:setWidth(self.columnWidth[colId] or self.defaultColumnWidth) + end + if column.height then + col:setHeight(column.height) + end + if column.text then + col:setText(column.text) + end + if column.sortvalue then + col.sortvalue = column.sortvalue + else + col.sortvalue = column.text or 0 + end + table.insert(self.columns[rowId], col) + end + + self.dataSpace:addChild(row) + table.insert(self.rows, row) + + if self.autoSort then + self:sort() + end + + return row +end + +-- Update row indices and background color +function UITable:updateRows() + for rowId = 1, #self.rows do + local row = self.rows[rowId] + row.rowId = rowId + row:setId('row'..rowId) + row:updateBackgroundColor() + end +end + +-- Removes the given row widget from the table +function UITable:removeRow(row) + if self.selectedRow == row then + self:selectRow(nil) + end + row.onClick = nil + row.table = nil + table.remove(self.columns, row.rowId) + table.remove(self.rows, row.rowId) + self.dataSpace:removeChild(row) + self:updateRows() +end + +function UITable:toggleSorting(enabled) + self.autoSort = enabled +end + +function UITable:setSorting(colId, sortType) + self.headerColumns[colId]:focus() + + if sortType then + self.sortType = sortType + elseif self.sortColumn == colId then + if self.sortType == TABLE_SORTING_ASC then + self.sortType = TABLE_SORTING_DESC + else + self.sortType = TABLE_SORTING_ASC + end + else + self.sortType = TABLE_SORTING_ASC + end + self.sortColumn = colId +end + +function UITable:sort() + if self.sortColumn <= 0 then + return + end + + if self.sortType == TABLE_SORTING_ASC then + table.sort(self.rows, function(rowA, b) + return rowA:getChildByIndex(self.sortColumn).sortvalue < b:getChildByIndex(self.sortColumn).sortvalue + end) + else + table.sort(self.rows, function(rowA, b) + return rowA:getChildByIndex(self.sortColumn).sortvalue > b:getChildByIndex(self.sortColumn).sortvalue + end) + end + + if self.dataSpace then + for _, child in pairs(self.dataSpace:getChildren()) do + self.dataSpace:removeChild(child) + end + end + + self:updateRows() + self.columns = {} + for _, row in pairs(self.rows) do + if self.dataSpace then + self.dataSpace:addChild(row) + end + + self.columns[row.rowId] = {} + for _, column in pairs(row:getChildren()) do + table.insert(self.columns[row.rowId], column) + end + end +end + +function UITable:selectRow(selectedRow) + if selectedRow == self.selectedRow then return end + + local previousSelectedRow = self.selectedRow + self.selectedRow = selectedRow + + if previousSelectedRow then + previousSelectedRow:setChecked(false) + end + + if selectedRow then + selectedRow:setChecked(true) + end + + signalcall(self.onSelectionChange, self, selectedRow, previousSelectedRow) +end + +function UITable:setTableData(tableData) + local headerHeight = 0 + if self.headerRow then + headerHeight = self.headerRow:getHeight() + end + + self.dataSpace = tableData + self.dataSpace:applyStyle({ height = self:getHeight()-headerHeight-self:getMarginTop() }) +end + +function UITable:setRowStyle(style, dontUpdate) + self.rowBaseStyle = style + + if not dontUpdate then + for _, row in pairs(self.rows) do + row:setStyle(style) + end + end +end + +function UITable:setColumnStyle(style, dontUpdate) + self.columBaseStyle = style + + if not dontUpdate then + for _, columns in pairs(self.columns) do + for _, col in pairs(columns) do + col:setStyle(style) + end + end + end +end + +function UITable:setHeaderRowStyle(style) + self.headerRowBaseStyle = style + if self.headerRow then + self.headerRow:setStyle(style) + end +end + +function UITable:setHeaderColumnStyle(style) + self.headerColumnBaseStyle = style + for _, col in pairs(self.headerColumns) do + col:setStyle(style) + end +end + + +UITableRow = extends(UIWidget, "UITableRow") + +function UITableRow:onFocusChange(focused) + if focused then + if self.table then self.table:selectRow(self) end + end +end + +function UITableRow:onStyleApply(styleName, styleNode) + for name,value in pairs(styleNode) do + if name == 'even-background-color' then + self.evenBackgroundColor = value + elseif name == 'odd-background-color' then + self.oddBackgroundColor = value + end + end +end + +function UITableRow:updateBackgroundColor() + self.backgroundColor = nil + + local isEven = (self.rowId % 2 == 0) + if isEven and self.evenBackgroundColor then + self.backgroundColor = self.evenBackgroundColor + elseif not isEven and self.oddBackgroundColor then + self.backgroundColor = self.oddBackgroundColor + end + + if self.backgroundColor then + self:mergeStyle({ ['background-color'] = self.backgroundColor }) + end +end + + +UITableHeaderColumn = extends(UIButton, "UITableHeaderColumn") + +function UITableHeaderColumn:onClick() + if self.table then + self.table:setSorting(self.colId) + self.table:sort() + end +end diff --git a/800OTClient/modules/corelib/ui/uitextedit.lua b/800OTClient/modules/corelib/ui/uitextedit.lua new file mode 100644 index 0000000..b671c7d --- /dev/null +++ b/800OTClient/modules/corelib/ui/uitextedit.lua @@ -0,0 +1,78 @@ +function UITextEdit:onStyleApply(styleName, styleNode) + for name,value in pairs(styleNode) do + if name == 'vertical-scrollbar' then + addEvent(function() + self:setVerticalScrollBar(self:getParent():getChildById(value)) + end) + elseif name == 'horizontal-scrollbar' then + addEvent(function() + self:setHorizontalScrollBar(self:getParent():getChildById(value)) + end) + end + end +end + +function UITextEdit:onMouseWheel(mousePos, mouseWheel) + if self.verticalScrollBar and self:isMultiline() then + if mouseWheel == MouseWheelUp then + self.verticalScrollBar:decrement() + else + self.verticalScrollBar:increment() + end + return true + elseif self.horizontalScrollBar then + if mouseWheel == MouseWheelUp then + self.horizontalScrollBar:increment() + else + self.horizontalScrollBar:decrement() + end + return true + end +end + +function UITextEdit:onTextAreaUpdate(virtualOffset, virtualSize, totalSize) + self:updateScrollBars() +end + +function UITextEdit:setVerticalScrollBar(scrollbar) + self.verticalScrollBar = scrollbar + self.verticalScrollBar.onValueChange = function(scrollbar, value) + local virtualOffset = self:getTextVirtualOffset() + virtualOffset.y = value + self:setTextVirtualOffset(virtualOffset) + end + self:updateScrollBars() +end + +function UITextEdit:setHorizontalScrollBar(scrollbar) + self.horizontalScrollBar = scrollbar + self.horizontalScrollBar.onValueChange = function(scrollbar, value) + local virtualOffset = self:getTextVirtualOffset() + virtualOffset.x = value + self:setTextVirtualOffset(virtualOffset) + end + self:updateScrollBars() +end + +function UITextEdit:updateScrollBars() + local scrollSize = self:getTextTotalSize() + local scrollWidth = math.max(scrollSize.width - self:getTextVirtualSize().width, 0) + local scrollHeight = math.max(scrollSize.height - self:getTextVirtualSize().height, 0) + + local scrollbar = self.verticalScrollBar + if scrollbar then + scrollbar:setMinimum(0) + scrollbar:setMaximum(scrollHeight) + scrollbar:setValue(self:getTextVirtualOffset().y) + end + + local scrollbar = self.horizontalScrollBar + if scrollbar then + scrollbar:setMinimum(0) + scrollbar:setMaximum(scrollWidth) + scrollbar:setValue(self:getTextVirtualOffset().x) + end + +end + +-- todo: ontext change, focus to cursor \ No newline at end of file diff --git a/800OTClient/modules/corelib/ui/uiwidget.lua b/800OTClient/modules/corelib/ui/uiwidget.lua new file mode 100644 index 0000000..a6007ca --- /dev/null +++ b/800OTClient/modules/corelib/ui/uiwidget.lua @@ -0,0 +1,21 @@ +-- @docclass UIWidget + +function UIWidget:setMargin(...) + local params = {...} + if #params == 1 then + self:setMarginTop(params[1]) + self:setMarginRight(params[1]) + self:setMarginBottom(params[1]) + self:setMarginLeft(params[1]) + elseif #params == 2 then + self:setMarginTop(params[1]) + self:setMarginRight(params[2]) + self:setMarginBottom(params[1]) + self:setMarginLeft(params[2]) + elseif #params == 4 then + self:setMarginTop(params[1]) + self:setMarginRight(params[2]) + self:setMarginBottom(params[3]) + self:setMarginLeft(params[4]) + end +end diff --git a/800OTClient/modules/corelib/ui/uiwindow.lua b/800OTClient/modules/corelib/ui/uiwindow.lua new file mode 100644 index 0000000..39208df --- /dev/null +++ b/800OTClient/modules/corelib/ui/uiwindow.lua @@ -0,0 +1,46 @@ +-- @docclass +UIWindow = extends(UIWidget, "UIWindow") + +function UIWindow.create() + local window = UIWindow.internalCreate() + window:setTextAlign(AlignTopCenter) + window:setDraggable(true) + window:setAutoFocusPolicy(AutoFocusFirst) + return window +end + +function UIWindow:onKeyDown(keyCode, keyboardModifiers) + if keyboardModifiers == KeyboardNoModifier then + if keyCode == KeyEnter then + signalcall(self.onEnter, self) + elseif keyCode == KeyEscape then + signalcall(self.onEscape, self) + end + end +end + +function UIWindow:onFocusChange(focused) + if focused then self:raise() end +end + +function UIWindow:onDragEnter(mousePos) + if self.static then + return false + end + self:breakAnchors() + self.movingReference = { x = mousePos.x - self:getX(), y = mousePos.y - self:getY() } + return true +end + +function UIWindow:onDragLeave(droppedWidget, mousePos) + -- TODO: auto detect and reconnect anchors +end + +function UIWindow:onDragMove(mousePos, mouseMoved) + if self.static then + return + end + local pos = { x = mousePos.x - self.movingReference.x, y = mousePos.y - self.movingReference.y } + self:setPosition(pos) + self:bindRectToParent() +end diff --git a/800OTClient/modules/corelib/util.lua b/800OTClient/modules/corelib/util.lua new file mode 100644 index 0000000..910f06f --- /dev/null +++ b/800OTClient/modules/corelib/util.lua @@ -0,0 +1,376 @@ +-- @docfuncs @{ + +function print(...) + local msg = "" + local args = {...} + local appendSpace = #args > 1 + for i,v in ipairs(args) do + msg = msg .. tostring(v) + if appendSpace and i < #args then + msg = msg .. ' ' + end + end + g_logger.log(LogInfo, msg) +end + +function pinfo(msg) + g_logger.log(LogInfo, msg) +end + +function perror(msg) + g_logger.log(LogError, msg) +end + +function pwarning(msg) + g_logger.log(LogWarning, msg) +end + +function pdebug(msg) + g_logger.log(LogDebug, msg) +end + +function fatal(msg) + g_logger.log(LogFatal, msg) +end + +function exit() + g_app.exit() +end + +function quit() + g_app.exit() +end + +function connect(object, arg1, arg2, arg3) + local signalsAndSlots + local pushFront + if type(arg1) == 'string' then + signalsAndSlots = { [arg1] = arg2 } + pushFront = arg3 + else + signalsAndSlots = arg1 + pushFront = arg2 + end + + for signal,slot in pairs(signalsAndSlots) do + if not object[signal] then + local mt = getmetatable(object) + if mt and type(object) == 'userdata' then + object[signal] = function(...) + return signalcall(mt[signal], ...) + end + end + end + + if not object[signal] then + object[signal] = slot + elseif type(object[signal]) == 'function' then + object[signal] = { object[signal] } + end + + if type(slot) ~= 'function' then + perror(debug.traceback('unable to connect a non function value')) + end + + if type(object[signal]) == 'table' then + if pushFront then + table.insert(object[signal], 1, slot) + else + table.insert(object[signal], #object[signal]+1, slot) + end + end + end +end + +function disconnect(object, arg1, arg2) + local signalsAndSlots + if type(arg1) == 'string' then + if arg2 == nil then + object[arg1] = nil + return + end + signalsAndSlots = { [arg1] = arg2 } + elseif type(arg1) == 'table' then + signalsAndSlots = arg1 + else + perror(debug.traceback('unable to disconnect')) + end + + for signal,slot in pairs(signalsAndSlots) do + if not object[signal] then + elseif type(object[signal]) == 'function' then + if object[signal] == slot then + object[signal] = nil + end + elseif type(object[signal]) == 'table' then + for k,func in pairs(object[signal]) do + if func == slot then + table.remove(object[signal], k) + + if #object[signal] == 1 then + object[signal] = object[signal][1] + end + break + end + end + end + end +end + +function newclass(name) + if not name then + perror(debug.traceback('new class has no name.')) + end + + local class = {} + function class.internalCreate() + local instance = {} + for k,v in pairs(class) do + instance[k] = v + end + return instance + end + class.create = class.internalCreate + class.__class = name + class.getClassName = function() return name end + return class +end + +function extends(base, name) + if not name then + perror(debug.traceback('extended class has no name.')) + end + + local derived = {} + function derived.internalCreate() + local instance = base.create() + for k,v in pairs(derived) do + instance[k] = v + end + return instance + end + derived.create = derived.internalCreate + derived.__class = name + derived.getClassName = function() return name end + return derived +end + +function runinsandbox(func, ...) + if type(func) == 'string' then + func, err = loadfile(resolvepath(func, 2)) + if not func then + error(err) + end + end + local env = { } + local oldenv = getfenv(0) + setmetatable(env, { __index = oldenv } ) + setfenv(0, env) + func(...) + setfenv(0, oldenv) + return env +end + +function loadasmodule(name, file) + file = file or resolvepath(name, 2) + if package.loaded[name] then + return package.loaded[name] + end + local env = runinsandbox(file) + package.loaded[name] = env + return env +end + +local function module_loader(modname) + local module = g_modules.getModule(modname) + if not module then + return '\n\tno module \'' .. modname .. '\'' + end + return function() + if not module:load() then + error('unable to load required module ' .. modname) + end + return module:getSandbox() + end +end +table.insert(package.loaders, 1, module_loader) + +function import(table) + assert(type(table) == 'table') + local env = getfenv(2) + for k,v in pairs(table) do + env[k] = v + end +end + +function export(what, key) + if key ~= nil then + _G[key] = what + else + for k,v in pairs(what) do + _G[k] = v + end + end +end + +function unexport(key) + if type(key) == 'table' then + for _k,v in pairs(key) do + _G[v] = nil + end + else + _G[key] = nil + end +end + +function getfsrcpath(depth) + depth = depth or 2 + local info = debug.getinfo(1+depth, "Sn") + local path + if info.short_src then + path = info.short_src:match("(.*)/.*") + end + if not path then + path = '/' + elseif path:sub(0, 1) ~= '/' then + path = '/' .. path + end + return path +end + +function resolvepath(filePath, depth) + if not filePath then return nil end + depth = depth or 1 + if filePath then + if filePath:sub(0, 1) ~= '/' then + local basepath = getfsrcpath(depth+1) + if basepath:sub(#basepath) ~= '/' then basepath = basepath .. '/' end + return basepath .. filePath + else + return filePath + end + else + local basepath = getfsrcpath(depth+1) + if basepath:sub(#basepath) ~= '/' then basepath = basepath .. '/' end + return basepath + end +end + +function toboolean(v) + if type(v) == 'string' then + v = v:trim():lower() + if v == '1' or v == 'true' then + return true + end + elseif type(v) == 'number' then + if v == 1 then + return true + end + elseif type(v) == 'boolean' then + return v + end + return false +end + +function fromboolean(boolean) + if boolean then + return 'true' + else + return 'false' + end +end + +function booleantonumber(boolean) + if boolean then + return 1 + else + return 0 + end +end + +function numbertoboolean(number) + if number ~= 0 then + return true + else + return false + end +end + +function protectedcall(func, ...) + local status, ret = pcall(func, ...) + if status then + return ret + end + + perror(ret) + return false +end + +function signalcall(param, ...) + if type(param) == 'function' then + local status, ret = pcall(param, ...) + if status then + return ret + else + perror(ret) + end + elseif type(param) == 'table' then + for k,v in pairs(param) do + local status, ret = pcall(v, ...) + if status then + if ret then return true end + else + perror(ret) + end + end + elseif param ~= nil then + error('attempt to call a non function value') + end + return false +end + +function tr(s, ...) + return string.format(s, ...) +end + +function getOppositeAnchor(anchor) + if anchor == AnchorLeft then + return AnchorRight + elseif anchor == AnchorRight then + return AnchorLeft + elseif anchor == AnchorTop then + return AnchorBottom + elseif anchor == AnchorBottom then + return AnchorTop + elseif anchor == AnchorVerticalCenter then + return AnchorHorizontalCenter + elseif anchor == AnchorHorizontalCenter then + return AnchorVerticalCenter + end + return anchor +end + +function makesingleton(obj) + local singleton = {} + if obj.getClassName then + for key,value in pairs(_G[obj:getClassName()]) do + if type(value) == 'function' then + singleton[key] = function(...) return value(obj, ...) end + end + end + end + return singleton +end + +function comma_value(amount) + local formatted = amount + while true do + formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2') + if (k==0) then + break + end + end + return formatted +end + +-- @} \ No newline at end of file diff --git a/800OTClient/modules/crash_reporter/crash_reporter.lua b/800OTClient/modules/crash_reporter/crash_reporter.lua new file mode 100644 index 0000000..1c4057f --- /dev/null +++ b/800OTClient/modules/crash_reporter/crash_reporter.lua @@ -0,0 +1,22 @@ +local CRASH_FILE = "exception.dmp" + +function init() + if g_resources.fileExists(CRASH_FILE) then + local crashLog = g_resources.readFileContents(CRASH_FILE) + local clientLog = g_logger.getLastLog() + HTTP.post(Services.crash, { + version = APP_VERSION, + build = g_app.getVersion(), + os = g_app.getOs(), + platform = g_window.getPlatformType(), + crash = base64.encode(crashLog), + log = base64.encode(clientLog) + }, function(data, err) + if err then + return g_logger.error("Error while reporting crash report: " .. err) + end + g_resources.deleteFile(CRASH_FILE) + end) + end +end + \ No newline at end of file diff --git a/800OTClient/modules/crash_reporter/crash_reporter.otmod b/800OTClient/modules/crash_reporter/crash_reporter.otmod new file mode 100644 index 0000000..8c879a0 --- /dev/null +++ b/800OTClient/modules/crash_reporter/crash_reporter.otmod @@ -0,0 +1,8 @@ +Module + name: crash_reporter + description: Sends crash log to remote server + author: otclient@otclient.ovh + website: otclient.ovh + reloadable: false + scripts: [ crash_reporter ] + @onLoad: init() diff --git a/800OTClient/modules/game_actionbar/actionbar.lua b/800OTClient/modules/game_actionbar/actionbar.lua new file mode 100644 index 0000000..d2b8135 --- /dev/null +++ b/800OTClient/modules/game_actionbar/actionbar.lua @@ -0,0 +1,620 @@ +bottomActionPanel1 = nil +bottomActionPanel2 = nil +bottomActionPanel3 = nil +leftActionPanel1 = nil +leftActionPanel2 = nil +leftActionPanel3 = nil + +local settings = {} +local hotkeyAssignWindow +local actionButtonsInPanel = 50 + +ActionTypes = { + USE = 0, + USE_SELF = 1, + USE_TARGET = 2, + USE_WITH = 3, + EQUIP = 4 +} + +ActionColors = { + empty = '#00000033', + text = '#00000033', + itemUse = '#8888FF88', + itemUseSelf = '#00FF0088', + itemUseTarget = '#FF000088', + itemUseWith = '#F5B32588', + itemEquip = '#FFFFFF88' +} + +function init() + local bottomPanel = modules.game_interface.getBottomActionPanel() + local leftPanel = modules.game_interface.getLeftActionPanel() + local rightPanel = modules.game_interface.getRightActionPanel() + + -- bottom + bottomActionPanel1 = g_ui.loadUI('actionbar', bottomPanel) + bottomPanel:moveChildToIndex(bottomActionPanel1, 1) + bottomActionPanel2 = g_ui.loadUI('actionbar', bottomPanel) + bottomPanel:moveChildToIndex(bottomActionPanel2, 1) + bottomActionPanel3 = g_ui.loadUI('actionbar', bottomPanel) + bottomPanel:moveChildToIndex(bottomActionPanel3, 1) + + -- left + leftActionPanel1 = g_ui.loadUI('sideactionbar', leftPanel) + leftPanel:moveChildToIndex(leftActionPanel1, 1) + leftActionPanel2 = g_ui.loadUI('sideactionbar', leftPanel) + leftPanel:moveChildToIndex(leftActionPanel2, 1) + leftActionPanel3 = g_ui.loadUI('sideactionbar', leftPanel) + leftPanel:moveChildToIndex(leftActionPanel3, 1) + + -- right + rightActionPanel1 = g_ui.loadUI('sideactionbar', rightPanel) + rightPanel:moveChildToIndex(rightActionPanel1, 1) + rightActionPanel2 = g_ui.loadUI('sideactionbar', rightPanel) + rightPanel:moveChildToIndex(rightActionPanel2, 1) + rightActionPanel3 = g_ui.loadUI('sideactionbar', rightPanel) + rightPanel:moveChildToIndex(rightActionPanel3, 1) + + connect(g_game, { + onGameStart = online, + onGameEnd = offline, + onSpellGroupCooldown = onSpellGroupCooldown, + onSpellCooldown = onSpellCooldown + }) + + if g_game.isOnline() then + online() + end +end + +function terminate() + disconnect(g_game, { + onGameStart = online, + onGameEnd = offline, + onSpellGroupCooldown = onSpellGroupCooldown, + onSpellCooldown = onSpellCooldown + }) + + -- remove hotkeys, also saves config + local panels = { + bottomActionPanel1, + bottomActionPanel2, + bottomActionPanel3, + leftActionPanel1, + leftActionPanel2, + leftActionPanel3, + rightActionPanel1, + rightActionPanel2, + rightActionPanel3, + } + + for i, panel in ipairs(panels) do + if panel.tabBar:getChildCount() > 0 then + offline() + break + end + end + + bottomActionPanel1:destroy() + bottomActionPanel2:destroy() + bottomActionPanel3:destroy() + leftActionPanel1:destroy() + leftActionPanel2:destroy() + leftActionPanel3:destroy() + rightActionPanel1:destroy() + rightActionPanel2:destroy() + rightActionPanel3:destroy() +end + +function show() + if not g_game.isOnline() then return end + bottomActionPanel1:setOn(g_settings.getBoolean("actionbarBottom1", false)) + bottomActionPanel2:setOn(g_settings.getBoolean("actionbarBottom2", false)) + bottomActionPanel3:setOn(g_settings.getBoolean("actionbarBottom3", false)) + leftActionPanel1:setOn(g_settings.getBoolean("actionbarLeft1", false)) + leftActionPanel2:setOn(g_settings.getBoolean("actionbarLeft2", false)) + leftActionPanel3:setOn(g_settings.getBoolean("actionbarLeft3", false)) + rightActionPanel1:setOn(g_settings.getBoolean("actionbarRight1", false)) + rightActionPanel2:setOn(g_settings.getBoolean("actionbarRight2", false)) + rightActionPanel3:setOn(g_settings.getBoolean("actionbarRight3", false)) +end + +function hide() + bottomActionPanel1:setOn(false) + bottomActionPanel2:setOn(false) + bottomActionPanel3:setOn(false) + leftActionPanel1:setOn(false) + leftActionPanel2:setOn(false) + leftActionPanel3:setOn(false) + rightActionPanel1:setOn(false) + rightActionPanel2:setOn(false) + rightActionPanel3:setOn(false) +end + +function switchMode(newMode) + if newMode then + bottomActionPanel1:setImageColor('#ffffff88') + bottomActionPanel2:setImageColor('#ffffff88') + bottomActionPanel3:setImageColor('#ffffff88') + leftActionPanel1:setImageColor('#ffffff88') + leftActionPanel2:setImageColor('#ffffff88') + leftActionPanel3:setImageColor('#ffffff88') + rightActionPanel1:setImageColor('#ffffff88') + rightActionPanel2:setImageColor('#ffffff88') + rightActionPanel3:setImageColor('#ffffff88') + else + bottomActionPanel1:setImageColor('white') + bottomActionPanel2:setImageColor('white') + bottomActionPanel3:setImageColor('white') + leftActionPanel1:setImageColor('white') + leftActionPanel2:setImageColor('white') + leftActionPanel3:setImageColor('white') + rightActionPanel1:setImageColor('white') + rightActionPanel2:setImageColor('white') + rightActionPanel3:setImageColor('white') + end +end + +function online() + load() + setupActionPanel(1, bottomActionPanel1, true) + setupActionPanel(2, bottomActionPanel2, true) + setupActionPanel(3, bottomActionPanel3, true) + setupActionPanel(4, leftActionPanel1, false) + setupActionPanel(5, leftActionPanel2, false) + setupActionPanel(6, leftActionPanel3, false) + setupActionPanel(7, rightActionPanel1, false) + setupActionPanel(8, rightActionPanel2, false) + setupActionPanel(9, rightActionPanel3, false) + show() +end + +function refresh(reloaded) + offline(reloaded) + online() +end + +function offline(reloaded) + hide() + if hotkeyAssignWindow then + hotkeyAssignWindow:destroy() + hotkeyAssignWindow = nil + end + + local gameRootPanel = modules.game_interface.getRootPanel() + for index, panel in ipairs({bottomActionPanel1, bottomActionPanel2, bottomActionPanel3, + leftActionPanel1, leftActionPanel2, leftActionPanel3, + rightActionPanel1, rightActionPanel2, rightActionPanel3}) do + settings[tostring(index)] = {} + for i, child in ipairs(panel.tabBar:getChildren()) do + if child.config and child.config.item then + settings[tostring(index)][tostring(i)] = child.config + if type(child.config.hotkey) == 'string' and child.config.hotkey:len() > 0 then + g_keyboard.unbindKeyPress(child.config.hotkey, child.callback, gameRootPanel) + end + end + if child.cooldownEvent then + removeEvent(child.cooldownEvent) + end + end + panel.tabBar:destroyChildren() + end + if not reloaded then + save() + end +end + +function setupActionPanel(index, panel, bottom) + local config = settings[tostring(index)] or {} + + for i=1,actionButtonsInPanel do + local type = bottom and 'ActionButton' or 'SideActionButton' + local action = g_ui.createWidget(type, panel.tabBar) + action.config = config[tostring(i)] or {} + setupAction(action) + end + + panel.nextButton.onClick = function() + panel.tabBar:moveChildToIndex(panel.tabBar:getFirstChild(), panel.tabBar:getChildCount()) + end + panel.prevButton.onClick = function() + panel.tabBar:moveChildToIndex(panel.tabBar:getLastChild(), 1) + end +end + +function setupAction(action) + local config = action.config + action.item:setShowCount(false) + action.onMouseRelease = actionOnMouseRelease + action.onTouchRelease = actionOnMouseRelease + + action.callback = function(k, c, ticks) + local lockKeyboard = g_settings.getBoolean('actionbarLock', false) + local chatMode = not modules.game_walking.wsadWalking + + if not lockKeyboard or not chatMode then + executeAction(action, ticks) + end + end + + action.item.onItemChange = nil -- disable callbacks for setup + + if config then + if type(config.text) == 'number' then + config.text = tostring(config.text) + end + if type(config.hotkey) == 'number' then + config.hotkey = tostring(config.hotkey) + end + if type(config.hotkey) == 'string' and config.hotkey:len() > 0 then + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.bindKeyPress(config.hotkey, action.callback, gameRootPanel) + local text = config.hotkey + -- formatting similar to cip Tibia 12 + local values = { + {"Shift", "S"}, + {"Ctrl", "C"}, + {"+", ""}, + {"PageUp", "PgUp"}, + {"PageDown", "PgDown"}, + {"Enter", "Return"}, + {"Insert", "Ins"}, + {"Delete", "Del"}, + {"Escape", "Esc"} + } + for i, v in pairs(values) do + text = text:gsub(v[1], v[2]) + end + if text:len() > 6 then + text = text:sub(text:len()-3,text:len()) + text = "..."..text + end + action.hotkeyLabel:setText(text) + else + action.hotkeyLabel:setText("") + end + + action.text:setImageSource("") + action.cooldownTill = 0 + action.cooldownStart = 0 + if type(config.text) == 'string' and config.text:len() > 0 then + action.text:setText(config.text) + action.item:setBorderColor(ActionColors.text) + action.item:setOn(true) -- removes background + action.item:setItemId(0) + if Spells then + local spell, profile = Spells.getSpellByWords(config.text:lower()) + action.spell = spell + if action.spell and action.spell.icon and profile then + action.text:setImageSource(SpelllistSettings[profile].iconFile) + action.text:setImageClip(Spells.getImageClip(SpellIcons[action.spell.icon][1], profile)) + action.text:setText("") + end + end + else + action.text:setText("") + action.spell = nil + if type(config.item) == 'number' and config.item > 100 then + action.item:setOn(true) + action.item:setItemId(config.item) + action.item:setItemCount(config.count or 1) + setupActionType(action, config.actionType) + else + action.item:setItemId(0) + action.item:setOn(false) + action.item:setBorderColor(ActionColors.empty) + end + end + end + + action.item.onItemChange = actionOnItemChange +end + +function setupActionType(action, actionType) + local item = action.item:getItem() + if action.item:getItem():isMultiUse() then + if not actionType or actionType <= ActionTypes.USE then + actionType = ActionTypes.USE_WITH + end + elseif g_game.getClientVersion() >= 910 then + if actionType ~= ActionTypes.USE and actionType ~= ActionTypes.EQUIP then + actionType = ActionTypes.USE + end + else + actionType = ActionTypes.USE + end + + action.config.actionType = actionType + if action.config.actionType == ActionTypes.USE then + action.item:setBorderColor(ActionColors.itemUse) + elseif action.config.actionType == ActionTypes.USE_SELF then + action.item:setBorderColor(ActionColors.itemUseSelf) + elseif action.config.actionType == ActionTypes.USE_TARGET then + action.item:setBorderColor(ActionColors.itemUseTarget) + elseif action.config.actionType == ActionTypes.USE_WITH then + action.item:setBorderColor(ActionColors.itemUseWith) + elseif action.config.actionType == ActionTypes.EQUIP then + action.item:setBorderColor(ActionColors.itemEquip) + end +end + +function updateAction(action, newConfig) + local config = action.config + if type(config.hotkey) == 'string' and config.hotkey:len() > 0 then + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.unbindKeyPress(config.hotkey, action.callback, gameRootPanel) + end + for key, val in pairs(newConfig) do + action.config[key] = val + end + setupAction(action) +end + +function actionOnMouseRelease(action, mousePosition, mouseButton) + if mouseButton == MouseTouch then return end + if mouseButton == MouseRightButton or not action.item:isOn() then + local menu = g_ui.createWidget('PopupMenu') + menu:setGameMenu(true) + if action.item:getItemId() > 0 then + if action.item:getItem():isMultiUse() then + menu:addOption(tr('Use on yourself'), function() return setupActionType(action, ActionTypes.USE_SELF) end) + menu:addOption(tr('Use on target'), function() return setupActionType(action, ActionTypes.USE_TARGET) end) + menu:addOption(tr('With crosshair'), function() return setupActionType(action, ActionTypes.USE_WITH) end) + end + if g_game.getClientVersion() >= 910 then + if not action.item:getItem():isMultiUse() then + menu:addOption(tr('Use'), function() return setupActionType(action, ActionTypes.USE) end) + end + menu:addOption(tr('Equip'), function() return setupActionType(action, ActionTypes.EQUIP) end) + end + else + menu:addOption(tr('Select item'), function() return modules.game_itemselector.show(action.item) end) + end + menu:addSeparator() + menu:addOption(tr('Set text'), function() + modules.client_textedit.singlelineEditor(action.config.text or "", function(newText) + updateAction(action, {text=newText, item=0}) + end) + end) + menu:addOption(tr('Set hotkey'), function() + if hotkeyAssignWindow then + hotkeyAssignWindow:destroy() + end + local assignWindow = g_ui.createWidget('ActionAssignWindow', rootWidget) + assignWindow:grabKeyboard() + assignWindow.comboPreview.keyCombo = '' + assignWindow.onKeyDown = function(assignWindow, keyCode, keyboardModifiers) + local keyCombo = determineKeyComboDesc(keyCode, keyboardModifiers) + assignWindow.comboPreview:setText(tr('Current action hotkey: %s', keyCombo)) + assignWindow.comboPreview.keyCombo = keyCombo + assignWindow.comboPreview:resizeToText() + return true + end + assignWindow.onDestroy = function(widget) + if widget == hotkeyAssignWindow then + hotkeyAssignWindow = nil + end + end + assignWindow.addButton.onClick = function() + local text = tostring(assignWindow.comboPreview.keyCombo) + updateAction(action, {hotkey=text}) + assignWindow:destroy() + end + hotkeyAssignWindow = assignWindow + end) + menu:addSeparator() + menu:addOption(tr('Clear'), function() + updateAction(action, {hotkey="", text="", item=0, count=1}) + end) + menu:display(mousePosition) + return true + elseif mouseButton == MouseLeftButton or mouseButton == MouseTouch2 or mouseButton == MouseTouch3 then + action.callback() + return true + end + return false +end + +function actionOnItemChange(widget) + updateAction(widget:getParent(), {text="", item=widget:getItemId(), count=widget:getItemCountOrSubType()}) +end + +function onSpellCooldown(iconId, duration) + for index, panel in ipairs({bottomActionPanel1, bottomActionPanel2, bottomActionPanel3, + leftActionPanel1, leftActionPanel2, leftActionPanel3, + rightActionPanel1, rightActionPanel2, rightActionPanel3}) do + for i, child in ipairs(panel.tabBar:getChildren()) do + if child.spell and child.spell.id == iconId then + startCooldown(child, duration) + end + end + end +end + +function onSpellGroupCooldown(groupId, duration) + for index, panel in ipairs({bottomActionPanel1, bottomActionPanel2, bottomActionPanel3, + leftActionPanel1, leftActionPanel2, leftActionPanel3, + rightActionPanel1, rightActionPanel2, rightActionPanel3}) do + for i, child in ipairs(panel.tabBar:getChildren()) do + if child.spell and child.spell.group then + for group, dur in pairs(child.spell.group) do + if groupId == group then + startCooldown(child, duration) + end + end + end + end + end +end + +function startCooldown(action, duration) + if type(action.cooldownTill) == 'number' and action.cooldownTill > g_clock.millis() + duration then + return -- already has cooldown with greater duration + end + action.cooldownStart = g_clock.millis() + action.cooldownTill = g_clock.millis() + duration + updateCooldown(action) +end + +function updateCooldown(action) + if not action or not action.cooldownTill then return end + local timeleft = action.cooldownTill - g_clock.millis() + if timeleft <= 30 then + action.cooldown:setPercent(100) + action.cooldownEvent = nil + action.cooldown:setText("") + return + end + local duration = action.cooldownTill - action.cooldownStart + local formattedText + if timeleft > 60000 then + formattedText = math.floor(timeleft / 60000) .. "m" + else + formattedText = timeleft/1000 + formattedText = math.floor(formattedText * 10) / 10 + formattedText = math.floor(formattedText) .. "." .. math.floor(formattedText * 10) % 10 + end + action.cooldown:setText(formattedText) + action.cooldown:setPercent(100 - math.floor(100 * timeleft / duration)) + action.cooldownEvent = scheduleEvent(function() updateCooldown(action) end, 30) +end + +function executeAction(action, ticks) + if not action.config then return end + if type(ticks) ~= 'number' then ticks = 0 end + + local actionDelay = 100 + if ticks == 0 then + actionDelay = 200 -- for first use + elseif action.actionDelayTo ~= nil and g_clock.millis() < action.actionDelayTo then + return + end + + local actionType = action.config.actionType + + if type(action.config.text) == 'string' and action.config.text:len() > 0 then + if g_app.isMobile() then -- turn to direction of targer + local target = g_game.getAttackingCreature() + if target then + local pos = g_game.getLocalPlayer():getPosition() + local tpos = target:getPosition() + if pos and tpos then + local offx = tpos.x - pos.x + local offy = tpos.y - pos.y + if offy < 0 and offx <= 0 and math.abs(offx) < math.abs(offy) then + g_game.turn(Directions.North) + elseif offy > 0 and offx >= 0 and math.abs(offx) < math.abs(offy) then + g_game.turn(Directions.South) + elseif offx < 0 and offy <= 0 and math.abs(offx) > math.abs(offy) then + g_game.turn(Directions.West) + elseif offx > 0 and offy >= 0 and math.abs(offx) > math.abs(offy) then + g_game.turn(Directions.East) + end + end + end + end + if modules.game_interface.isChatVisible() then + modules.game_console.sendMessage(action.config.text) + else + g_game.talk(action.config.text) + end + action.actionDelayTo = g_clock.millis() + actionDelay + elseif action.item:getItemId() > 0 then + if actionType == ActionTypes.USE then + if g_game.getClientVersion() < 780 then + local item = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1) + if item then + g_game.use(item) + end + else + g_game.useInventoryItem(action.item:getItemId()) + end + action.actionDelayTo = g_clock.millis() + actionDelay + elseif actionType == ActionTypes.USE_SELF then + if g_game.getClientVersion() < 780 then + local item = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1) + if item then + g_game.useWith(item, g_game.getLocalPlayer()) + end + else + g_game.useInventoryItemWith(action.item:getItemId(), g_game.getLocalPlayer(), action.item:getItemSubType() or -1) + end + action.actionDelayTo = g_clock.millis() + actionDelay + elseif actionType == ActionTypes.USE_TARGET then + local attackingCreature = g_game.getAttackingCreature() + if not attackingCreature then + local item = Item.create(action.item:getItemId()) + if g_game.getClientVersion() < 780 then + local tmpItem = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1) + if not tmpItem then return end + item = tmpItem + end + + modules.game_interface.startUseWith(item, action.item:getItemSubType() or - 1) + return + end + + if not attackingCreature:getTile() then return end + if g_game.getClientVersion() < 780 then + local item = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1) + if item then + g_game.useWith(item, attackingCreature, action.item:getItemSubType() or -1) + end + else + g_game.useInventoryItemWith(action.item:getItemId(), attackingCreature, action.item:getItemSubType() or -1) + end + action.actionDelayTo = g_clock.millis() + actionDelay + elseif actionType == ActionTypes.USE_WITH then + local item = Item.create(action.item:getItemId()) + if g_game.getClientVersion() < 780 then + local tmpItem = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1) + if not tmpItem then return true end + item = tmpItem + end + modules.game_interface.startUseWith(item, action.item:getItemSubType() or - 1) + elseif actionType == ActionTypes.EQUIP then + if g_game.getClientVersion() >= 910 then + local item = Item.create(action.item:getItemId()) + g_game.equipItem(item) + action.actionDelayTo = g_clock.millis() + actionDelay + end + end + end +end + +function save() + local settingsFile = modules.client_profiles.getSettingsFilePath("actionbar.json") + + local status, result = pcall(function() return json.encode(settings, 2) end) + if not status then + return onError( + "Error while saving top bar settings. Data won't be saved. Details: " .. + result) + end + + if result:len() > 100 * 1024 * 1024 then + return onError( + "Something went wrong, file is above 100MB, won't be saved") + end + + g_resources.writeFileContents(settingsFile, result) +end + +function load() + local settingsFile = modules.client_profiles.getSettingsFilePath("actionbar.json") + + if g_resources.fileExists(settingsFile) then + local status, result = pcall(function() + return json.decode(g_resources.readFileContents(settingsFile)) + end) + if not status then + return onError( + "Error while reading top bar settings file. To fix this problem you can delete storage.json. Details: " .. + result) + end + settings = result + else + settings = {} + end +end diff --git a/800OTClient/modules/game_actionbar/actionbar.otmod b/800OTClient/modules/game_actionbar/actionbar.otmod new file mode 100644 index 0000000..b11c62c --- /dev/null +++ b/800OTClient/modules/game_actionbar/actionbar.otmod @@ -0,0 +1,9 @@ +Module + name: game_actionbar + description: Action bar + author: otclient@otclient.ovh + website: otclient.ovh + sandboxed: true + scripts: [ actionbar ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_actionbar/actionbar.otui b/800OTClient/modules/game_actionbar/actionbar.otui new file mode 100644 index 0000000..31a6c2e --- /dev/null +++ b/800OTClient/modules/game_actionbar/actionbar.otui @@ -0,0 +1,141 @@ +ActionButton < Panel + font: cipsoftFont + width: 40 + padding: 1 1 1 1 + margin-left: 1 + draggable: true + + Item + id: item + anchors.fill: parent + &selectable: true + &editable: false + virtual: true + border-width: 1 + + border-color: #00000000 + + $!on: + image-source: /images/game/actionbarslot + + Label + id: text + anchors.fill: parent + text-auto-resize: true + text-wrap: true + phantom: true + text-align: center + font: verdana-9px + + Label + id: hotkeyLabel + anchors.top: parent.top + anchors.right: parent.right + margin: 2 3 3 3 + text-auto-resize: true + text-wrap: false + phantom: true + font: cipsoftFont + color: white + background: #292A2A + + UIProgressRect + id: cooldown + background: #585858AA + percent: 100 + focusable: false + phantom: true + anchors.fill: parent + margin: 1 1 1 1 + font: verdana-11px-rounded + color: white + +Panel + id: actionBar + focusable: false + image-source: /images/ui/panel_side + image-border: 4 + margin-top: -1 + + $on: + height: 40 + visible: true + + $!on: + height: 0 + visible: false + + TabButton + id: prevButton + icon: /images/game/console/leftarrow + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + margin-left: 1 + margin-top: 1 + margin-bottom: 2 + + Panel + id: tabBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: prev.right + anchors.right: next.left + margin-right: 3 + clipping: true + layout: horizontalBox + + TabButton + id: nextButton + icon: /images/game/console/rightarrow + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + margin-right: 1 + margin-top: 1 + margin-bottom: 2 + +ActionAssignWindow < MainWindow + id: assignWindow + !text: tr('Button Assign') + size: 360 150 + @onEscape: self:destroy() + + Label + !text: tr('Please, press the key you wish to use for action') + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-auto-resize: true + text-align: left + + Label + id: comboPreview + !text: tr('Current action hotkey: %s', 'none') + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: prev.bottom + margin-top: 10 + text-auto-resize: true + + HorizontalSeparator + id: separator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + margin-bottom: 10 + + Button + id: addButton + !text: tr('Add') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + + Button + id: cancelButton + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: self:getParent():destroy() \ No newline at end of file diff --git a/800OTClient/modules/game_actionbar/sideactionbar.otui b/800OTClient/modules/game_actionbar/sideactionbar.otui new file mode 100644 index 0000000..c21010e --- /dev/null +++ b/800OTClient/modules/game_actionbar/sideactionbar.otui @@ -0,0 +1,145 @@ +SideActionButton < Panel + font: cipsoftFont + height: 40 + padding: 1 1 1 1 + + $first: + margin-top: -4 + + $!first: + margin-top: -1 + + Item + id: item + anchors.fill: parent + &selectable: true + &editable: false + virtual: true + border-width: 1 + + border-color: #00000000 + + $!on: + image-source: /images/game/actionbarslot + + Label + id: text + anchors.fill: parent + text-auto-resize: true + text-wrap: true + phantom: true + text-align: center + font: verdana-9px + + Label + id: hotkeyLabel + anchors.top: parent.top + anchors.right: parent.right + margin: 2 3 3 3 + text-auto-resize: true + text-wrap: false + phantom: true + font: cipsoftFont + color: white + background: #292A2A + + UIProgressRect + id: cooldown + background: #585858AA + percent: 100 + focusable: false + phantom: true + anchors.fill: parent + margin: 1 1 1 1 + font: verdana-11px-rounded + color: white + +Panel + id: actionBar + focusable: false + image-source: /images/ui/panel_side + image-border: 4 + margin-top: -2 + + $on: + width: 40 + visible: true + + $!on: + width: 0 + visible: false + + TabButton + id: prevButton + icon: /images/game/console/uparrow + anchors.left: parent.left + anchors.top: parent.top + anchors.right: parent.right + margin-top: -1 + margin-left: 1 + margin-right: 1 + + Panel + id: tabBar + anchors.top: prev.bottom + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: next.top + margin-top: 3 + clipping: true + layout: verticalBox + + TabButton + id: nextButton + icon: /images/game/console/downarrow + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-right: 1 + margin-left: 1 + margin-bottom: 2 + +ActionAssignWindow < MainWindow + id: assignWindow + !text: tr('Button Assign') + size: 360 150 + @onEscape: self:destroy() + + Label + !text: tr('Please, press the key you wish to use for action') + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-auto-resize: true + text-align: left + + Label + id: comboPreview + !text: tr('Current action hotkey: %s', 'none') + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: prev.bottom + margin-top: 10 + text-auto-resize: true + + HorizontalSeparator + id: separator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + margin-bottom: 10 + + Button + id: addButton + !text: tr('Add') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + + Button + id: cancelButton + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: self:getParent():destroy() \ No newline at end of file diff --git a/800OTClient/modules/game_battle/battle.lua b/800OTClient/modules/game_battle/battle.lua new file mode 100644 index 0000000..bc2e03c --- /dev/null +++ b/800OTClient/modules/game_battle/battle.lua @@ -0,0 +1,458 @@ +battleWindow = nil +battleButton = nil +battlePanel = nil +filterPanel = nil +toggleFilterButton = nil + +mouseWidget = nil +updateEvent = nil + +hoveredCreature = nil +newHoveredCreature = nil +prevCreature = nil + +battleButtons = {} +local ageNumber = 1 +local ages = {} + +function init() + g_ui.importStyle('battlebutton') + battleButton = modules.client_topmenu.addRightGameToggleButton('battleButton', tr('Battle') .. ' (Ctrl+B)', '/images/topbuttons/battle', toggle, false, 2) + battleButton:setOn(true) + battleWindow = g_ui.loadUI('battle', modules.game_interface.getRightPanel()) + g_keyboard.bindKeyDown('Ctrl+B', toggle) + + -- this disables scrollbar auto hiding + local scrollbar = battleWindow:getChildById('miniwindowScrollBar') + scrollbar:mergeStyle({ ['$!on'] = { }}) + + battlePanel = battleWindow:recursiveGetChildById('battlePanel') + + filterPanel = battleWindow:recursiveGetChildById('filterPanel') + toggleFilterButton = battleWindow:recursiveGetChildById('toggleFilterButton') + + if isHidingFilters() then + hideFilterPanel() + end + + local sortTypeBox = filterPanel.sortPanel.sortTypeBox + local sortOrderBox = filterPanel.sortPanel.sortOrderBox + + mouseWidget = g_ui.createWidget('UIButton') + mouseWidget:setVisible(false) + mouseWidget:setFocusable(false) + mouseWidget.cancelNextRelease = false + + battleWindow:setContentMinimumHeight(80) + + sortTypeBox:addOption('Name', 'name') + sortTypeBox:addOption('Distance', 'distance') + sortTypeBox:addOption('Total age', 'age') + sortTypeBox:addOption('Screen age', 'screenage') + sortTypeBox:addOption('Health', 'health') + sortTypeBox:setCurrentOptionByData(getSortType()) + sortTypeBox.onOptionChange = onChangeSortType + + sortOrderBox:addOption('Asc.', 'asc') + sortOrderBox:addOption('Desc.', 'desc') + sortOrderBox:setCurrentOptionByData(getSortOrder()) + sortOrderBox.onOptionChange = onChangeSortOrder + + battleWindow:setup() + + for i=1,30 do + local battleButton = g_ui.createWidget('BattleButton', battlePanel) + battleButton:setup() + battleButton:hide() + battleButton.onHoverChange = onBattleButtonHoverChange + battleButton.onMouseRelease = onBattleButtonMouseRelease + table.insert(battleButtons, battleButton) + end + + updateBattleList() + + connect(LocalPlayer, { + onPositionChange = onPlayerPositionChange + }) + connect(Creature, { + onAppear = updateSquare, + onDisappear = updateSquare + }) + connect(g_game, { + onAttackingCreatureChange = updateSquare, + onFollowingCreatureChange = updateSquare + }) +end + +function terminate() + if battleButton == nil then + return + end + + battleButtons = {} + + g_keyboard.unbindKeyDown('Ctrl+B') + battleButton:destroy() + battleWindow:destroy() + mouseWidget:destroy() + + disconnect(LocalPlayer, { + onPositionChange = onPlayerPositionChange + }) + disconnect(Creature, { + onAppear = onCreatureAppear, + onDisappear = onCreatureDisappear + }) + disconnect(g_game, { + onAttackingCreatureChange = updateSquare, + onFollowingCreatureChange = updateSquare + }) + + removeEvent(updateEvent) +end + +function toggle() + if battleButton:isOn() then + battleWindow:close() + battleButton:setOn(false) + else + battleWindow:open() + battleButton:setOn(true) + end +end + +function onMiniWindowClose() + battleButton:setOn(false) +end + +function getSortType() + local settings = g_settings.getNode('BattleList') + if not settings then + if g_app.isMobile() then + return 'distance' + else + return 'name' + end + end + return settings['sortType'] +end + +function setSortType(state) + settings = {} + settings['sortType'] = state + g_settings.mergeNode('BattleList', settings) + + checkCreatures() +end + +function getSortOrder() + local settings = g_settings.getNode('BattleList') + if not settings then + return 'asc' + end + return settings['sortOrder'] +end + +function setSortOrder(state) + settings = {} + settings['sortOrder'] = state + g_settings.mergeNode('BattleList', settings) + + checkCreatures() +end + +function isSortAsc() + return getSortOrder() == 'asc' +end + +function isSortDesc() + return getSortOrder() == 'desc' +end + +function isHidingFilters() + local settings = g_settings.getNode('BattleList') + if not settings then + return false + end + return settings['hidingFilters'] +end + +function setHidingFilters(state) + settings = {} + settings['hidingFilters'] = state + g_settings.mergeNode('BattleList', settings) +end + +function hideFilterPanel() + filterPanel.originalHeight = filterPanel:getHeight() + filterPanel:setHeight(0) + toggleFilterButton:getParent():setMarginTop(0) + toggleFilterButton:setImageClip(torect("0 0 21 12")) + setHidingFilters(true) + filterPanel:setVisible(false) +end + +function showFilterPanel() + toggleFilterButton:getParent():setMarginTop(5) + filterPanel:setHeight(filterPanel.originalHeight) + toggleFilterButton:setImageClip(torect("21 0 21 12")) + setHidingFilters(false) + filterPanel:setVisible(true) +end + +function toggleFilterPanel() + if filterPanel:isVisible() then + hideFilterPanel() + else + showFilterPanel() + end +end + +function onChangeSortType(comboBox, option, value) + setSortType(value:lower()) +end + +function onChangeSortOrder(comboBox, option, value) + -- Replace dot in option name + setSortOrder(value:lower():gsub('[.]', '')) +end + +-- functions +function updateBattleList() + removeEvent(updateEvent) + updateEvent = scheduleEvent(updateBattleList, 100) + checkCreatures() +end + +function checkCreatures() + if not battlePanel or not g_game.isOnline() then + return + end + + local player = g_game.getLocalPlayer() + if not player then + return + end + + local dimension = modules.game_interface.getMapPanel():getVisibleDimension() + local spectators = g_map.getSpectatorsInRangeEx(player:getPosition(), false, math.floor(dimension.width / 2), math.floor(dimension.width / 2), math.floor(dimension.height / 2), math.floor(dimension.height / 2)) + local maxCreatures = battlePanel:getChildCount() + + local creatures = {} + local now = g_clock.millis() + local resetAgePoint = now - 250 + for _, creature in ipairs(spectators) do + if doCreatureFitFilters(creature) and #creatures < maxCreatures then + if not creature.lastSeen or creature.lastSeen < resetAgePoint then + creature.screenAge = now + end + creature.lastSeen = now + if not ages[creature:getId()] then + if ageNumber > 1000 then + ageNumber = 1 + ages = {} + end + ages[creature:getId()] = ageNumber + ageNumber = ageNumber + 1 + end + table.insert(creatures, creature) + end + end + + updateSquare() + sortCreatures(creatures) + battlePanel:getLayout():disableUpdates() + + -- sorting + local ascOrder = isSortAsc() + for i=1,#creatures do + local creature = creatures[i] + if ascOrder then + creature = creatures[#creatures - i + 1] + end + local battleButton = battleButtons[i] + battleButton:creatureSetup(creature) + battleButton:show() + battleButton:setOn(true) + end + + if g_app.isMobile() and #creatures > 0 then + onBattleButtonHoverChange(battleButtons[1], true) + end + + for i=#creatures + 1,maxCreatures do + if battleButtons[i]:isHidden() then break end + battleButtons[i]:hide() + battleButton:setOn(false) + end + + battlePanel:getLayout():enableUpdates() + battlePanel:getLayout():update() +end + +function doCreatureFitFilters(creature) + if creature:isLocalPlayer() then + return false + end + if creature:getHealthPercent() <= 0 then + return false + end + + local pos = creature:getPosition() + if not pos then return false end + + local localPlayer = g_game.getLocalPlayer() + if pos.z ~= localPlayer:getPosition().z or not creature:canBeSeen() then return false end + + local hidePlayers = filterPanel.buttons.hidePlayers:isChecked() + local hideNPCs = filterPanel.buttons.hideNPCs:isChecked() + local hideMonsters = filterPanel.buttons.hideMonsters:isChecked() + local hideSkulls = filterPanel.buttons.hideSkulls:isChecked() + local hideParty = filterPanel.buttons.hideParty:isChecked() + + if hidePlayers and creature:isPlayer() then + return false + elseif hideNPCs and creature:isNpc() then + return false + elseif hideMonsters and creature:isMonster() then + return false + elseif hideSkulls and creature:isPlayer() and creature:getSkull() == SkullNone then + return false + elseif hideParty and creature:getShield() > ShieldWhiteBlue then + return false + end + + return true +end + +local function getDistanceBetween(p1, p2) + return math.max(math.abs(p1.x - p2.x), math.abs(p1.y - p2.y)) +end + +function sortCreatures(creatures) + local player = g_game.getLocalPlayer() + + if getSortType() == 'distance' then + local playerPos = player:getPosition() + table.sort(creatures, function(a, b) + if getDistanceBetween(playerPos, a:getPosition()) == getDistanceBetween(playerPos, b:getPosition()) then + return ages[a:getId()] > ages[b:getId()] + end + return getDistanceBetween(playerPos, a:getPosition()) > getDistanceBetween(playerPos, b:getPosition()) + end) + elseif getSortType() == 'health' then + table.sort(creatures, function(a, b) + if a:getHealthPercent() == b:getHealthPercent() then + return ages[a:getId()] > ages[b:getId()] + end + return a:getHealthPercent() > b:getHealthPercent() + end) + elseif getSortType() == 'age' then + table.sort(creatures, function(a, b) return ages[a:getId()] > ages[b:getId()] end) + elseif getSortType() == 'screenage' then + table.sort(creatures, function(a, b) return a.screenAge > b.screenAge end) + else -- name + table.sort(creatures, function(a, b) + if a:getName():lower() == b:getName():lower() then + return ages[a:getId()] > ages[b:getId()] + end + return a:getName():lower() > b:getName():lower() + end) + end +end + +-- other functions +function onBattleButtonMouseRelease(self, mousePosition, mouseButton) + if mouseWidget.cancelNextRelease then + mouseWidget.cancelNextRelease = false + return false + end + if not self.creature then + return false + end + if ((g_mouse.isPressed(MouseLeftButton) and mouseButton == MouseRightButton) + or (g_mouse.isPressed(MouseRightButton) and mouseButton == MouseLeftButton)) then + mouseWidget.cancelNextRelease = true + g_game.look(self.creature, true) + return true + elseif mouseButton == MouseLeftButton and g_keyboard.isShiftPressed() then + g_game.look(self.creature, true) + return true + elseif mouseButton == MouseRightButton and not g_mouse.isPressed(MouseLeftButton) then + modules.game_interface.createThingMenu(mousePosition, nil, nil, self.creature) + return true + elseif mouseButton == MouseLeftButton and not g_mouse.isPressed(MouseRightButton) then + if self.isTarget then + g_game.cancelAttack() + else + g_game.attack(self.creature) + end + return true + end + return false +end + +function onBattleButtonHoverChange(battleButton, hovered) + if not hovered then + newHoveredCreature = nil + else + newHoveredCreature = battleButton.creature + end + if battleButton.isHovered ~= hovered then + battleButton.isHovered = hovered + battleButton:update() + end + updateSquare() +end + +function onPlayerPositionChange(creature, newPos, oldPos) + addEvent(checkCreatures) +end + +local CreatureButtonColors = { + onIdle = {notHovered = '#888888', hovered = '#FFFFFF' }, + onTargeted = {notHovered = '#FF0000', hovered = '#FF8888' }, + onFollowed = {notHovered = '#00FF00', hovered = '#88FF88' } +} + +function updateSquare() + local following = g_game.getFollowingCreature() + local attacking = g_game.getAttackingCreature() + + if newHoveredCreature == nil then + if hoveredCreature ~= nil then + hoveredCreature:hideStaticSquare() + hoveredCreature = nil + end + else + if hoveredCreature ~= nil then + hoveredCreature:hideStaticSquare() + end + hoveredCreature = newHoveredCreature + hoveredCreature:showStaticSquare(CreatureButtonColors.onIdle.hovered) + end + + local color = CreatureButtonColors.onIdle + local creature = nil + if attacking then + color = CreatureButtonColors.onTargeted + creature = attacking + elseif following then + color = CreatureButtonColors.onFollowed + creature = following + end + + if prevCreature ~= creature then + if prevCreature ~= nil then + prevCreature:hideStaticSquare() + end + prevCreature = creature + end + + if not creature then + return + end + + color = creature == hoveredCreature and color.hovered or color.notHovered + creature:showStaticSquare(color) +end \ No newline at end of file diff --git a/800OTClient/modules/game_battle/battle.otmod b/800OTClient/modules/game_battle/battle.otmod new file mode 100644 index 0000000..ba50425 --- /dev/null +++ b/800OTClient/modules/game_battle/battle.otmod @@ -0,0 +1,9 @@ +Module + name: game_battle + description: Manage battle window (new) + author: otclient@otclient.ovh + website: otclient.ovh + sandboxed: true + scripts: [ battle ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_battle/battle.otui b/800OTClient/modules/game_battle/battle.otui new file mode 100644 index 0000000..c3af2e3 --- /dev/null +++ b/800OTClient/modules/game_battle/battle.otui @@ -0,0 +1,155 @@ +BattleIcon < UICheckBox + size: 20 20 + image-color: white + image-rect: 0 0 20 20 + + $hover !disabled: + color: #cccccc + + $!checked: + image-clip: 0 0 20 20 + + $hover !checked: + image-clip: 0 40 20 20 + + $checked: + image-clip: 0 20 20 20 + + $hover checked: + image-clip: 0 60 20 20 + + $disabled: + image-color: #ffffff88 + +BattlePlayers < BattleIcon + image-source: /images/game/battle/battle_players + +BattleNPCs < BattleIcon + image-source: /images/game/battle/battle_npcs + +BattleMonsters < BattleIcon + image-source: /images/game/battle/battle_monsters + +BattleSkulls < BattleIcon + image-source: /images/game/battle/battle_skulls + +BattleParty < BattleIcon + image-source: /images/game/battle/battle_party + +MiniWindow + id: battleWindow + !text: tr('Battle') + height: 166 + icon: /images/topbuttons/battle + @onClose: modules.game_battle.onMiniWindowClose() + &save: true + &autoOpen: false + + Panel + id: filterPanel + margin-top: 26 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: miniwindowScrollBar.left + height: 45 + + Panel + id: buttons + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + height: 20 + width: 120 + layout: + type: horizontalBox + spacing: 5 + + BattlePlayers + id: hidePlayers + !tooltip: tr('Hide players') + @onSetup: if g_app.isMobile() then self:setChecked(true) end + @onCheckChange: modules.game_battle.checkCreatures() + + BattleNPCs + id: hideNPCs + !tooltip: tr('Hide Npcs') + @onSetup: if g_app.isMobile() then self:setChecked(true) end + @onCheckChange: modules.game_battle.checkCreatures() + + BattleMonsters + id: hideMonsters + !tooltip: tr('Hide monsters') + @onCheckChange: modules.game_battle.checkCreatures() + + BattleSkulls + id: hideSkulls + !tooltip: tr('Hide non-skull players') + @onCheckChange: modules.game_battle.checkCreatures() + + BattleParty + id: hideParty + !tooltip: tr('Hide party members') + @onSetup: if g_app.isMobile() then self:setChecked(true) end + @onCheckChange: modules.game_battle.checkCreatures() + + Panel + id: sortPanel + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 20 + margin-top: 6 + + ComboBox + id: sortTypeBox + width: 90 + anchors.top: parent.top + anchors.left: prev.right + anchors.horizontalCenter: parent.horizontalCenter + margin-left: -31 + + ComboBox + id: sortOrderBox + width: 60 + anchors.top: parent.top + anchors.left: prev.right + margin-left: 4 + + Panel + height: 18 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: miniwindowScrollBar.left + margin-top: 4 + + UIWidget + id: toggleFilterButton + anchors.top: prev.top + width: 21 + anchors.horizontalCenter: parent.horizontalCenter + image-source: /images/ui/arrow_vertical + image-rect: 0 0 21 12 + image-clip: 21 0 21 12 + @onClick: modules.game_battle.toggleFilterPanel() + phantom: false + + HorizontalSeparator + anchors.top: prev.top + anchors.left: parent.left + anchors.right: miniwindowScrollBar.left + margin-right: 1 + margin-top: 11 + + MiniWindowContents + anchors.top: prev.bottom + margin-top: 6 + + Panel + id: battlePanel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + margin-top: 5 + padding-right: 5 + layout: + type: verticalBox + fit-children: true diff --git a/800OTClient/modules/game_battle/battlebutton.otui b/800OTClient/modules/game_battle/battlebutton.otui new file mode 100644 index 0000000..38a0158 --- /dev/null +++ b/800OTClient/modules/game_battle/battlebutton.otui @@ -0,0 +1,2 @@ +BattleButton < CreatureButton + &isBattleButton: true diff --git a/800OTClient/modules/game_bot/bot.lua b/800OTClient/modules/game_bot/bot.lua new file mode 100644 index 0000000..7b0fea5 --- /dev/null +++ b/800OTClient/modules/game_bot/bot.lua @@ -0,0 +1,792 @@ +botWindow = nil +botButton = nil +contentsPanel = nil +editWindow = nil + +local checkEvent = nil + +local botStorage = {} +local botStorageFile = nil +local botWebSockets = {} +local botMessages = nil +local botTabs = nil +local botExecutor = nil + +local configList = nil +local enableButton = nil +local executeEvent = nil +local statusLabel = nil + +local configManagerUrl = "http://otclient.ovh/configs.php" + +function init() + dofile("executor") + + g_ui.importStyle("ui/basic.otui") + g_ui.importStyle("ui/panels.otui") + g_ui.importStyle("ui/config.otui") + g_ui.importStyle("ui/icons.otui") + g_ui.importStyle("ui/container.otui") + + connect(g_game, { + onGameStart = online, + onGameEnd = offline, + }) + + initCallbacks() + + botButton = modules.client_topmenu.addRightGameToggleButton('botButton', tr('Bot'), '/images/topbuttons/bot', toggle, false, 99999) + botButton:setOn(false) + botButton:hide() + + botWindow = g_ui.loadUI('bot', modules.game_interface.getLeftPanel()) + botWindow:setup() + + contentsPanel = botWindow.contentsPanel + configList = contentsPanel.config + enableButton = contentsPanel.enableButton + statusLabel = contentsPanel.statusLabel + botMessages = contentsPanel.messages + botTabs = contentsPanel.botTabs + botTabs:setContentWidget(contentsPanel.botPanel) + + editWindow = g_ui.displayUI('edit') + editWindow:hide() + + if g_game.isOnline() then + clear() + online() + end +end + +function terminate() + save() + clear() + + disconnect(g_game, { + onGameStart = online, + onGameEnd = offline, + }) + + terminateCallbacks() + editWindow:destroy() + + botWindow:destroy() + botButton:destroy() +end + +function clear() + botExecutor = nil + removeEvent(checkEvent) + + -- optimization, callback is not used when not needed + g_game.enableTileThingLuaCallback(false) + + botTabs:clearTabs() + botTabs:setOn(false) + + botMessages:destroyChildren() + botMessages:updateLayout() + + for i, socket in pairs(botWebSockets) do + g_http.cancel(socket) + botWebSockets[i] = nil + end + + for i, widget in pairs(g_ui.getRootWidget():getChildren()) do + if widget.botWidget then + widget:destroy() + end + end + for i, widget in pairs(modules.game_interface.gameMapPanel:getChildren()) do + if widget.botWidget then + widget:destroy() + end + end + for _, widget in pairs({modules.game_interface.getRightPanel(), modules.game_interface.getLeftPanel()}) do + for i, child in pairs(widget:getChildren()) do + if child.botWidget then + child:destroy() + end + end + end + + local gameMapPanel = modules.game_interface.getMapPanel() + if gameMapPanel then + gameMapPanel:unlockVisibleFloor() + end + + if g_sounds then + g_sounds.getChannel(SoundChannels.Bot):stop() + end +end + + +function refresh() + if not g_game.isOnline() then return end + save() + clear() + + -- create bot dir + if not g_resources.directoryExists("/bot") then + g_resources.makeDir("/bot") + if not g_resources.directoryExists("/bot") then + return onError("Can't create bot directory in " .. g_resources.getWriteDir()) + end + end + + -- get list of configs + createDefaultConfigs() + local configs = g_resources.listDirectoryFiles("/bot", false, false) + + -- clean + configList.onOptionChange = nil + enableButton.onClick = nil + configList:clearOptions() + + -- select active config based on settings + local settings = g_settings.getNode('bot') or {} + local index = g_game.getCharacterName() .. "_" .. g_game.getClientVersion() + if settings[index] == nil then + settings[index] = { + enabled=false, + config="" + } + end + + -- init list and buttons + for i=1,#configs do + configList:addOption(configs[i]) + end + configList:setCurrentOption(settings[index].config) + if configList:getCurrentOption().text ~= settings[index].config then + settings[index].config = configList:getCurrentOption().text + settings[index].enabled = false + end + + enableButton:setOn(settings[index].enabled) + + configList.onOptionChange = function(widget) + settings[index].config = widget:getCurrentOption().text + g_settings.setNode('bot', settings) + g_settings.save() + refresh() + end + + enableButton.onClick = function(widget) + settings[index].enabled = not settings[index].enabled + g_settings.setNode('bot', settings) + g_settings.save() + refresh() + end + + if not g_game.isOnline() or not settings[index].enabled then + statusLabel:setOn(true) + statusLabel:setText("Status: disabled\nPress off button to enable") + return + end + + local configName = settings[index].config + + -- storage + botStorage = {} + + local path = "/bot/" .. configName .. "/storage/" + if not g_resources.directoryExists(path) then + g_resources.makeDir(path) + end + + botStorageFile = path.."profile_" .. g_settings.getNumber('profile') .. ".json" + if g_resources.fileExists(botStorageFile) then + local status, result = pcall(function() + return json.decode(g_resources.readFileContents(botStorageFile)) + end) + if not status then + return onError("Error while reading storage (" .. botStorageFile .. "). To fix this problem you can delete storage.json. Details: " .. result) + end + botStorage = result + end + + -- run script + local status, result = pcall(function() + return executeBot(configName, botStorage, botTabs, message, save, refresh, botWebSockets) end + ) + if not status then + return onError(result) + end + + statusLabel:setOn(false) + botExecutor = result + check() +end + +function save() + if not botExecutor then + return + end + + local settings = g_settings.getNode('bot') or {} + local index = g_game.getCharacterName() .. "_" .. g_game.getClientVersion() + if settings[index] == nil then + return + end + + local status, result = pcall(function() + return json.encode(botStorage, 2) + end) + if not status then + return onError("Error while saving bot storage. Storage won't be saved. Details: " .. result) + end + + if result:len() > 100 * 1024 * 1024 then + return onError("Storage file is too big, above 100MB, it won't be saved") + end + + g_resources.writeFileContents(botStorageFile, result) +end + +function onMiniWindowClose() + botButton:setOn(false) +end + +function toggle() + if botButton:isOn() then + botWindow:close() + botButton:setOn(false) + else + botWindow:open() + botButton:setOn(true) + end +end + +function online() + botButton:show() + if not modules.client_profiles.ChangedProfile then + scheduleEvent(refresh, 20) + end +end + +function offline() + save() + clear() + botButton:hide() + editWindow:hide() +end + +function onError(message) + statusLabel:setOn(true) + statusLabel:setText("Error:\n" .. message) + g_logger.error("[BOT] " .. message) +end + +function edit() + local configs = g_resources.listDirectoryFiles("/bot", false, false) + editWindow.manager.upload.config:clearOptions() + for i=1,#configs do + editWindow.manager.upload.config:addOption(configs[i]) + end + editWindow.manager.download.config:setText("") + + editWindow:show() + editWindow:focus() + editWindow:raise() +end + +function createDefaultConfigs() + local defaultConfigFiles = g_resources.listDirectoryFiles("default_configs", false, false) + for i, config_name in ipairs(defaultConfigFiles) do + if not g_resources.directoryExists("/bot/" .. config_name) then + g_resources.makeDir("/bot/" .. config_name) + if not g_resources.directoryExists("/bot/" .. config_name) then + return onError("Can't create /bot/" .. config_name .. " directory in " .. g_resources.getWriteDir()) + end + + local defaultConfigFiles = g_resources.listDirectoryFiles("default_configs/" .. config_name, true, false) + for i, file in ipairs(defaultConfigFiles) do + local baseName = file:split("/") + baseName = baseName[#baseName] + if g_resources.directoryExists(file) then + g_resources.makeDir("/bot/" .. config_name .. "/" .. baseName) + if not g_resources.directoryExists("/bot/" .. config_name .. "/" .. baseName) then + return onError("Can't create /bot/" .. config_name .. "/" .. baseName .. " directory in " .. g_resources.getWriteDir()) + end + local defaultConfigFiles2 = g_resources.listDirectoryFiles("default_configs/" .. config_name .. "/" .. baseName, true, false) + for i, file in ipairs(defaultConfigFiles2) do + local baseName2 = file:split("/") + baseName2 = baseName2[#baseName2] + local contents = g_resources.fileExists(file) and g_resources.readFileContents(file) or "" + if contents:len() > 0 then + g_resources.writeFileContents("/bot/" .. config_name .. "/" .. baseName .. "/" .. baseName2, contents) + end + end + else + local contents = g_resources.fileExists(file) and g_resources.readFileContents(file) or "" + if contents:len() > 0 then + g_resources.writeFileContents("/bot/" .. config_name .. "/" .. baseName, contents) + end + end + end + end + end +end + +function uploadConfig() + local config = editWindow.manager.upload.config:getCurrentOption().text + local archive = compressConfig(config) + if not archive then + return displayErrorBox(tr("Config upload failed"), tr("Config %s is invalid (can't be compressed)", config)) + end + if archive:len() > 1024 * 1024 then + return displayErrorBox(tr("Config upload failed"), tr("Config %s is too big, maximum size is 1024KB. Now it has %s KB.", config, math.floor(archive:len() / 1024))) + end + + local infoBox = displayInfoBox(tr("Uploading config"), tr("Uploading config %s. Please wait.", config)) + + HTTP.postJSON(configManagerUrl .. "?config=" .. config:gsub("%s+", "_"), archive, function(data, err) + if infoBox then + infoBox:destroy() + end + if err or data["error"] then + return displayErrorBox(tr("Config upload failed"), tr("Error while upload config %s:\n%s", config, err or data["error"])) + end + displayInfoBox(tr("Succesful config upload"), tr("Config %s has been uploaded.\n%s", config, data["message"])) + end) +end + +function downloadConfig() + local hash = editWindow.manager.download.config:getText() + if hash:len() == 0 then + return displayErrorBox(tr("Config download error"), tr("Enter correct config hash")) + end + local infoBox = displayInfoBox(tr("Downloading config"), tr("Downloading config with hash %s. Please wait.", hash)) + HTTP.download(configManagerUrl .. "?hash=" .. hash, hash .. ".zip", function(path, checksum, err) + if infoBox then + infoBox:destroy() + end + if err then + return displayErrorBox(tr("Config download error"), tr("Config with hash %s cannot be downloaded", hash)) + end + modules.client_textedit.show("", { + title="Enter name for downloaded config", + description="Config with hash " .. hash .. " has been downloaded. Enter name for new config.\nWarning: if config with same name already exist, it will be overwritten!", + width=500 + }, function(configName) + decompressConfig(configName, "/downloads/" .. path) + refresh() + edit() + end) + end) +end + +function compressConfig(configName) + if not g_resources.directoryExists("/bot/" .. configName) then + return onError("Config " .. configName .. " doesn't exist") + end + local forArchive = {} + for _, file in ipairs(g_resources.listDirectoryFiles("/bot/" .. configName)) do + local fullPath = "/bot/" .. configName .. "/" .. file + if g_resources.fileExists(fullPath) then -- regular file + forArchive[file] = g_resources.readFileContents(fullPath) + else -- dir + for __, file2 in ipairs(g_resources.listDirectoryFiles(fullPath)) do + local fullPath2 = fullPath .. "/" .. file2 + if g_resources.fileExists(fullPath2) then -- regular file + forArchive[file .. "/" .. file2] = g_resources.readFileContents(fullPath2) + end + end + end + end + return g_resources.createArchive(forArchive) +end + +function decompressConfig(configName, archive) + if g_resources.directoryExists("/bot/" .. configName) then + g_resources.deleteFile("/bot/" .. configName) -- also delete dirs + end + local files = g_resources.decompressArchive(archive) + g_resources.makeDir("/bot/" .. configName) + if not g_resources.directoryExists("/bot/" .. configName) then + return onError("Can't create /bot/" .. configName .. " directory in " .. g_resources.getWriteDir()) + end + + for file, contents in pairs(files) do + local split = file:split("/") + split[#split] = nil -- remove file name + local dirPath = "/bot/" .. configName + for _, s in ipairs(split) do + dirPath = dirPath .. "/" .. s + if not g_resources.directoryExists(dirPath) then + g_resources.makeDir(dirPath) + if not g_resources.directoryExists(dirPath) then + return onError("Can't create " .. dirPath .. " directory in " .. g_resources.getWriteDir()) + end + end + end + g_resources.writeFileContents("/bot/" .. configName .. file, contents) + end +end + +-- Executor +function message(category, msg) + local widget = g_ui.createWidget('BotLabel', botMessages) + widget.added = g_clock.millis() + if category == 'error' then + widget:setText(msg) + widget:setColor("red") + g_logger.error("[BOT] " .. msg) + elseif category == 'warn' then + widget:setText(msg) + widget:setColor("yellow") + g_logger.warning("[BOT] " .. msg) + elseif category == 'info' then + widget:setText(msg) + widget:setColor("white") + g_logger.info("[BOT] " .. msg) + end + + if botMessages:getChildCount() > 5 then + botMessages:getFirstChild():destroy() + end +end + +function check() + removeEvent(checkEvent) + if not botExecutor then + return + end + + checkEvent = scheduleEvent(check, 10) + + local status, result = pcall(function() + return botExecutor.script() + end) + if not status then + botExecutor = nil -- critical + return onError(result) + end + + -- remove old messages + local widget = botMessages:getFirstChild() + if widget and widget.added + 5000 < g_clock.millis() then + widget:destroy() + end +end + +-- Callbacks +function initCallbacks() + connect(rootWidget, { + onKeyDown = botKeyDown, + onKeyUp = botKeyUp, + onKeyPress = botKeyPress + }) + + connect(g_game, { + onTalk = botOnTalk, + onTextMessage = botOnTextMessage, + onLoginAdvice = botOnLoginAdvice, + onUse = botOnUse, + onUseWith = botOnUseWith, + onChannelList = botChannelList, + onOpenChannel = botOpenChannel, + onCloseChannel = botCloseChannel, + onChannelEvent = botChannelEvent, + onImbuementWindow = botImbuementWindow, + onModalDialog = botModalDialog, + onAttackingCreatureChange = botAttackingCreatureChange, + onAddItem = botContainerAddItem, + onRemoveItem = botContainerRemoveItem, + onGameEditText = botGameEditText, + onSpellCooldown = botSpellCooldown, + onSpellGroupCooldown = botGroupSpellCooldown + }) + + connect(Tile, { + onAddThing = botAddThing, + onRemoveThing = botRemoveThing + }) + + connect(Creature, { + onAppear = botCreatureAppear, + onDisappear = botCreatureDisappear, + onPositionChange = botCreaturePositionChange, + onHealthPercentChange = botCraetureHealthPercentChange, + onTurn = botCreatureTurn, + onWalk = botCreatureWalk, + }) + + connect(LocalPlayer, { + onPositionChange = botCreaturePositionChange, + onHealthPercentChange = botCraetureHealthPercentChange, + onTurn = botCreatureTurn, + onWalk = botCreatureWalk, + onManaChange = botManaChange, + onStatesChange = botStatesChange + }) + + connect(Container, { + onOpen = botContainerOpen, + onClose = botContainerClose, + onUpdateItem = botContainerUpdateItem, + onAddItem = botContainerAddItem, + onRemoveItem = botContainerRemoveItem, + }) + + connect(g_map, { + onMissle = botOnMissle, + onAnimatedText = botOnAnimatedText, + onStaticText = botOnStaticText + }) +end + +function terminateCallbacks() + disconnect(rootWidget, { + onKeyDown = botKeyDown, + onKeyUp = botKeyUp, + onKeyPress = botKeyPress + }) + + disconnect(g_game, { + onTalk = botOnTalk, + onTextMessage = botOnTextMessage, + onLoginAdvice = botOnLoginAdvice, + onUse = botOnUse, + onUseWith = botOnUseWith, + onChannelList = botChannelList, + onOpenChannel = botOpenChannel, + onCloseChannel = botCloseChannel, + onChannelEvent = botChannelEvent, + onImbuementWindow = botImbuementWindow, + onModalDialog = botModalDialog, + onAttackingCreatureChange = botAttackingCreatureChange, + onGameEditText = botGameEditText, + onSpellCooldown = botSpellCooldown, + onSpellGroupCooldown = botGroupSpellCooldown + }) + + disconnect(Tile, { + onAddThing = botAddThing, + onRemoveThing = botRemoveThing + }) + + disconnect(Creature, { + onAppear = botCreatureAppear, + onDisappear = botCreatureDisappear, + onPositionChange = botCreaturePositionChange, + onHealthPercentChange = botCraetureHealthPercentChange, + onTurn = botCreatureTurn, + onWalk = botCreatureWalk, + }) + + disconnect(LocalPlayer, { + onPositionChange = botCreaturePositionChange, + onHealthPercentChange = botCraetureHealthPercentChange, + onTurn = botCreatureTurn, + onWalk = botCreatureWalk, + onManaChange = botManaChange, + onStatesChange = botStatesChange + }) + + disconnect(Container, { + onOpen = botContainerOpen, + onClose = botContainerClose, + onUpdateItem = botContainerUpdateItem, + onAddItem = botContainerAddItem, + onRemoveItem = botContainerRemoveItem + }) + + disconnect(g_map, { + onMissle = botOnMissle, + onAnimatedText = botOnAnimatedText, + onStaticText = botOnStaticText + }) +end + +function safeBotCall(func) + local status, result = pcall(func) + if not status then + onError(result) + end +end + +function botKeyDown(widget, keyCode, keyboardModifiers) + if botExecutor == nil then return false end + if keyCode == KeyUnknown then return end + safeBotCall(function() botExecutor.callbacks.onKeyDown(keyCode, keyboardModifiers) end) +end + +function botKeyUp(widget, keyCode, keyboardModifiers) + if botExecutor == nil then return false end + if keyCode == KeyUnknown then return end + safeBotCall(function() botExecutor.callbacks.onKeyUp(keyCode, keyboardModifiers) end) +end + +function botKeyPress(widget, keyCode, keyboardModifiers, autoRepeatTicks) + if botExecutor == nil then return false end + if keyCode == KeyUnknown then return end + safeBotCall(function() botExecutor.callbacks.onKeyPress(keyCode, keyboardModifiers, autoRepeatTicks) end) +end + +function botOnTalk(name, level, mode, text, channelId, pos) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onTalk(name, level, mode, text, channelId, pos) end) +end + +function botOnTextMessage(mode, text) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onTextMessage(mode, text) end) +end + +function botOnLoginAdvice(message) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onLoginAdvice(message) end) +end + +function botAddThing(tile, thing) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onAddThing(tile, thing) end) +end + +function botRemoveThing(tile, thing) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onRemoveThing(tile, thing) end) +end + +function botCreatureAppear(creature) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onCreatureAppear(creature) end) +end + +function botCreatureDisappear(creature) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onCreatureDisappear(creature) end) +end + +function botCreaturePositionChange(creature, newPos, oldPos) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onCreaturePositionChange(creature, newPos, oldPos) end) +end + +function botCraetureHealthPercentChange(creature, healthPercent) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onCreatureHealthPercentChange(creature, healthPercent) end) +end + +function botOnUse(pos, itemId, stackPos, subType) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onUse(pos, itemId, stackPos, subType) end) +end + +function botOnUseWith(pos, itemId, target, subType) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onUseWith(pos, itemId, target, subType) end) +end + +function botContainerOpen(container, previousContainer) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onContainerOpen(container, previousContainer) end) +end + +function botContainerClose(container) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onContainerClose(container) end) +end + +function botContainerUpdateItem(container, slot, item, oldItem) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onContainerUpdateItem(container, slot, item, oldItem) end) +end + +function botOnMissle(missle) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onMissle(missle) end) +end + +function botOnAnimatedText(thing, text) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onAnimatedText(thing, text) end) +end + +function botOnStaticText(thing, text) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onStaticText(thing, text) end) +end + +function botChannelList(channels) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onChannelList(channels) end) +end + +function botOpenChannel(channelId, name) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onOpenChannel(channelId, name) end) +end + +function botCloseChannel(channelId) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onCloseChannel(channelId) end) +end + +function botChannelEvent(channelId, name, event) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onChannelEvent(channelId, name, event) end) +end + +function botCreatureTurn(creature, direction) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onTurn(creature, direction) end) +end + +function botCreatureWalk(creature, oldPos, newPos) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onWalk(creature, oldPos, newPos) end) +end + +function botImbuementWindow(itemId, slots, activeSlots, imbuements, needItems) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onImbuementWindow(itemId, slots, activeSlots, imbuements, needItems) end) +end + +function botModalDialog(id, title, message, buttons, enterButton, escapeButton, choices, priority) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onModalDialog(id, title, message, buttons, enterButton, escapeButton, choices, priority) end) +end + +function botGameEditText(id, itemId, maxLength, text, writer, time) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onGameEditText(id, itemId, maxLength, text, writer, time) end) +end + +function botAttackingCreatureChange(creature, oldCreature) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onAttackingCreatureChange(creature,oldCreature) end) +end + +function botManaChange(player, mana, maxMana, oldMana, oldMaxMana) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onManaChange(player, mana, maxMana, oldMana, oldMaxMana) end) +end + +function botStatesChange(states, oldStates) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onStatesChange(states, oldStates) end) +end + +function botContainerAddItem(container, slot, item, oldItem) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onAddItem(container, slot, item, oldItem) end) +end + +function botContainerRemoveItem(container, slot, item) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onRemoveItem(container, slot, item) end) +end + +function botSpellCooldown(iconId, duration) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onSpellCooldown(iconId, duration) end) +end + +function botGroupSpellCooldown(iconId, duration) + if botExecutor == nil then return false end + safeBotCall(function() botExecutor.callbacks.onGroupSpellCooldown(iconId, duration) end) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/bot.otmod b/800OTClient/modules/game_bot/bot.otmod new file mode 100644 index 0000000..adaebf2 --- /dev/null +++ b/800OTClient/modules/game_bot/bot.otmod @@ -0,0 +1,8 @@ +Module + name: game_bot + description: Advanced OTClientV8 Bot + author: otclient@otclient.ovh + sandboxed: true + scripts: [ bot ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_bot/bot.otui b/800OTClient/modules/game_bot/bot.otui new file mode 100644 index 0000000..c1f6061 --- /dev/null +++ b/800OTClient/modules/game_bot/bot.otui @@ -0,0 +1,126 @@ +BotTabBar < TabBar + tab-spacing: 1 + margin-left: 1 + margin-right: 1 + height: 20 + + $on: + visible: true + margin-top: 2 + + $!on: + visible: false + margin-top: -20 + +BotTabBarPanel < TabBarPanel +BotTabBarButton < TabBarButton + padding: 4 + padding-right: 5 + text-horizontal-auto-resize: true + $!first: + margin-left: 0 + +MiniWindow + id: botWindow + !text: tr('Bot') + height: 600 + icon: /images/topbuttons/bot + @onClose: modules.game_bot.onMiniWindowClose() + &save: true + &autoOpen: 10 + + MiniWindowContents + ComboBox + id: config + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + margin-top: 2 + margin-left: 2 + margin-right: 75 + text-offset: 3 0 + + Button + id: editConfig + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + !text: tr('Edit') + @onClick: modules.game_bot.edit() + margin-left: 3 + margin-right: 37 + + Button + id: enableButton + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 3 + margin-right: 2 + + $on: + text: On + color: #00AA00 + + $!on: + text: Off + color: #FF0000 + + Label + id: statusLabel + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + text-wrap: true + text-auto-resize: true + text-align: center + !text: tr('Status: waiting') + margin-left: 3 + margin-right: 3 + + $on: + margin-top: 3 + + $!on: + text: + margin-top: -13 + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 3 + margin-left: 2 + margin-right: 2 + + Panel + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + id: messages + layout: + type: verticalBox + fit-children: true + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 5 + margin-left: 2 + margin-right: 2 + + BotTabBar + id: botTabs + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-right: -20 + + Panel + id: botPanel + margin-top: 2 + anchors.top: prev.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right diff --git a/800OTClient/modules/game_bot/configs.png b/800OTClient/modules/game_bot/configs.png new file mode 100644 index 0000000..1e7ff71 Binary files /dev/null and b/800OTClient/modules/game_bot/configs.png differ diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot.lua new file mode 100644 index 0000000..a325693 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot.lua @@ -0,0 +1,39 @@ +-- Cavebot by otclient@otclient.ovh +-- visit http://bot.otclient.ovh/ + +local cavebotTab = "Cave" +local targetingTab = "Target" + +setDefaultTab(cavebotTab) +CaveBot = {} -- global namespace +CaveBot.Extensions = {} +importStyle("/cavebot/cavebot.otui") +importStyle("/cavebot/config.otui") +importStyle("/cavebot/editor.otui") +importStyle("/cavebot/supply.otui") +dofile("/cavebot/actions.lua") +dofile("/cavebot/config.lua") +dofile("/cavebot/editor.lua") +dofile("/cavebot/example_functions.lua") +dofile("/cavebot/recorder.lua") +dofile("/cavebot/walking.lua") +-- in this section you can add extensions, check extension_template.lua +--dofile("/cavebot/extension_template.lua") +dofile("/cavebot/depositer.lua") +dofile("/cavebot/supply.lua") +-- main cavebot file, must be last +dofile("/cavebot/cavebot.lua") + +setDefaultTab(targetingTab) +TargetBot = {} -- global namespace +importStyle("/targetbot/looting.otui") +importStyle("/targetbot/target.otui") +importStyle("/targetbot/creature_editor.otui") +dofile("/targetbot/creature.lua") +dofile("/targetbot/creature_attack.lua") +dofile("/targetbot/creature_editor.lua") +dofile("/targetbot/creature_priority.lua") +dofile("/targetbot/looting.lua") +dofile("/targetbot/walking.lua") +-- main targetbot file, must be last +dofile("/targetbot/target.lua") diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/actions.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/actions.lua new file mode 100644 index 0000000..64d7288 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/actions.lua @@ -0,0 +1,264 @@ +CaveBot.Actions = {} + +-- it adds an action widget to list +CaveBot.addAction = function(action, value, focus) + action = action:lower() + local raction = CaveBot.Actions[action] + if not raction then + return error("Invalid cavebot action: " .. action) + end + if type(value) == 'number' then + value = tostring(value) + end + local widget = UI.createWidget("CaveBotAction", CaveBot.actionList) + widget:setText(action .. ":" .. value:split("\n")[1]) + widget.action = action + widget.value = value + if raction.color then + widget:setColor(raction.color) + end + widget.onDoubleClick = function(cwidget) -- edit on double click + if CaveBot.Editor then + schedule(20, function() -- schedule to have correct focus + CaveBot.Editor.edit(cwidget.action, cwidget.value, function(action, value) + CaveBot.editAction(cwidget, action, value) + CaveBot.save() + end) + end) + end + end + if focus then + widget:focus() + CaveBot.actionList:ensureChildVisible(widget) + end + return widget +end + +-- it updates existing widget, you should call CaveBot.save() later +CaveBot.editAction = function(widget, action, value) + action = action:lower() + local raction = CaveBot.Actions[action] + if not raction then + return error("Invalid cavebot action: " .. action) + end + + if not widget.action or not widget.value then + return error("Invalid cavebot action widget, has missing action or value") + end + + widget:setText(action .. ":" .. value:split("\n")[1]) + widget.action = action + widget.value = value + if raction.color then + widget:setColor(raction.color) + end + return widget +end + +--[[ +registerAction: +action - string, color - string, callback = function(value, retries, prev) +value is a string value of action, retries is number which will grow by 1 if return is "retry" +prev is a true when previuos action was executed succesfully, false otherwise +it must return true if executed correctly, false otherwise +it can also return string "retry", then the function will be called again in 20 ms +]]-- +CaveBot.registerAction = function(action, color, callback) + action = action:lower() + if CaveBot.Actions[action] then + return error("Duplicated acction: " .. action) + end + CaveBot.Actions[action] = { + color=color, + callback=callback + } +end + +CaveBot.registerAction("label", "yellow", function(value, retries, prev) + return true +end) + +CaveBot.registerAction("gotolabel", "#FFFF55", function(value, retries, prev) + return CaveBot.gotoLabel(value) +end) + +CaveBot.registerAction("delay", "#AAAAAA", function(value, retries, prev) + if retries == 0 then + CaveBot.delay(tonumber(value)) + return "retry" + end + return true +end) + +CaveBot.registerAction("function", "red", function(value, retries, prev) + local prefix = "local retries = " .. retries .. "\nlocal prev = " .. tostring(prev) .. "\nlocal delay = CaveBot.delay\nlocal gotoLabel = CaveBot.gotoLabel\n" + prefix = prefix .. "local macro = function() error('Macros inside cavebot functions are not allowed') end\n" + for extension, callbacks in pairs(CaveBot.Extensions) do + prefix = prefix .. "local " .. extension .. " = CaveBot.Extensions." .. extension .. "\n" + end + local status, result = pcall(function() + return assert(load(prefix .. value, "cavebot_function"))() + end) + if not status then + error("Error in cavebot function:\n" .. result) + return false + end + return result +end) + +CaveBot.registerAction("goto", "green", function(value, retries, prev) + local pos = regexMatch(value, "\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+),?\\s*([0-9]?)") + if not pos[1] then + error("Invalid cavebot goto action value. It should be position (x,y,z), is: " .. value) + return false + end + + if CaveBot.Config.get("mapClick") then + if retries >= 5 then + return false -- tried 5 times, can't get there + end + else + if retries >= 100 then + return false -- tried 100 times, can't get there + end + end + + local precision = tonumber(pos[1][5]) + pos = {x=tonumber(pos[1][2]), y=tonumber(pos[1][3]), z=tonumber(pos[1][4])} + local playerPos = player:getPosition() + if pos.z ~= playerPos.z then + return false -- different floor + end + + if math.abs(pos.x-playerPos.x) + math.abs(pos.y-playerPos.y) > 40 then + return false -- too far way + end + + local minimapColor = g_map.getMinimapColor(pos) + local stairs = (minimapColor >= 210 and minimapColor <= 213) + + if stairs then + if math.abs(pos.x-playerPos.x) == 0 and math.abs(pos.y-playerPos.y) <= 0 then + return true -- already at position + end + elseif math.abs(pos.x-playerPos.x) == 0 and math.abs(pos.y-playerPos.y) <= (precision or 1) then + return true -- already at position + end + -- check if there's a path to that place, ignore creatures and fields + local path = findPath(playerPos, pos, 40, { ignoreNonPathable = true, precision = 1, ignoreCreatures = true }) + if not path then + return false -- there's no way + end + + -- try to find path, don't ignore creatures, don't ignore fields + if not CaveBot.Config.get("ignoreFields") and CaveBot.walkTo(pos, 40) then + return "retry" + end + + -- try to find path, don't ignore creatures, ignore fields + if CaveBot.walkTo(pos, 40, { ignoreNonPathable = true }) then + return "retry" + end + + if retries >= 3 then + -- try to lower precision, find something close to final position + local precison = retries - 1 + if stairs then + precison = 0 + end + if CaveBot.walkTo(pos, 50, { ignoreNonPathable = true, precision = precison }) then + return "retry" + end + end + + if not CaveBot.Config.get("mapClick") and retries >= 5 then + return false + end + + if CaveBot.Config.get("skipBlocked") then + return false + end + + -- everything else failed, try to walk ignoring creatures, maybe will work + CaveBot.walkTo(pos, 40, { ignoreNonPathable = true, precision = 1, ignoreCreatures = true }) + return "retry" +end) + +CaveBot.registerAction("use", "#FFB272", function(value, retries, prev) + local pos = regexMatch(value, "\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)") + if not pos[1] then + local itemid = tonumber(value) + if not itemid then + error("Invalid cavebot use action value. It should be (x,y,z) or item id, is: " .. value) + return false + end + use(itemid) + return true + end + + pos = {x=tonumber(pos[1][2]), y=tonumber(pos[1][3]), z=tonumber(pos[1][4])} + local playerPos = player:getPosition() + if pos.z ~= playerPos.z then + return false -- different floor + end + + if math.max(math.abs(pos.x-playerPos.x), math.abs(pos.y-playerPos.y)) > 7 then + return false -- too far way + end + + local tile = g_map.getTile(pos) + if not tile then + return false + end + + local topThing = tile:getTopUseThing() + if not topThing then + return false + end + + use(topThing) + CaveBot.delay(CaveBot.Config.get("useDelay") + CaveBot.Config.get("ping")) + return true +end) + +CaveBot.registerAction("usewith", "#EEB292", function(value, retries, prev) + local pos = regexMatch(value, "\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)") + if not pos[1] then + if not itemid then + error("Invalid cavebot usewith action value. It should be (itemid,x,y,z) or item id, is: " .. value) + return false + end + use(itemid) + return true + end + + local itemid = tonumber(pos[1][2]) + pos = {x=tonumber(pos[1][3]), y=tonumber(pos[1][4]), z=tonumber(pos[1][5])} + local playerPos = player:getPosition() + if pos.z ~= playerPos.z then + return false -- different floor + end + + if math.max(math.abs(pos.x-playerPos.x), math.abs(pos.y-playerPos.y)) > 7 then + return false -- too far way + end + + local tile = g_map.getTile(pos) + if not tile then + return false + end + + local topThing = tile:getTopUseThing() + if not topThing then + return false + end + + usewith(itemid, topThing) + CaveBot.delay(CaveBot.Config.get("useDelay") + CaveBot.Config.get("ping")) + return true +end) + +CaveBot.registerAction("say", "#FF55FF", function(value, retries, prev) + say(value) + return true +end) diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/cavebot.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/cavebot.lua new file mode 100644 index 0000000..a00ee71 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/cavebot.lua @@ -0,0 +1,224 @@ +local cavebotMacro = nil +local config = nil + +-- ui +local configWidget = UI.Config() +local ui = UI.createWidget("CaveBotPanel") + +ui.list = ui.listPanel.list -- shortcut +CaveBot.actionList = ui.list + +if CaveBot.Editor then + CaveBot.Editor.setup() +end +if CaveBot.Config then + CaveBot.Config.setup() +end +for extension, callbacks in pairs(CaveBot.Extensions) do + if callbacks.setup then + callbacks.setup() + end +end + +-- main loop, controlled by config +local actionRetries = 0 +local prevActionResult = true +cavebotMacro = macro(20, function() + if TargetBot and TargetBot.isActive() and not TargetBot.isCaveBotActionAllowed() then + CaveBot.resetWalking() + return -- target bot or looting is working, wait + end + + if CaveBot.doWalking() then + return -- executing walking + end + + local actions = ui.list:getChildCount() + if actions == 0 then return end + local currentAction = ui.list:getFocusedChild() + if not currentAction then + currentAction = ui.list:getFirstChild() + end + local action = CaveBot.Actions[currentAction.action] + local value = currentAction.value + local retry = false + if action then + local status, result = pcall(function() + CaveBot.resetWalking() + return action.callback(value, actionRetries, prevActionResult) + end) + if status then + if result == "retry" then + actionRetries = actionRetries + 1 + retry = true + elseif type(result) == 'boolean' then + actionRetries = 0 + prevActionResult = result + else + error("Invalid return from cavebot action (" .. currentAction.action .. "), should be \"retry\", false or true, is: " .. tostring(result)) + end + else + error("Error while executing cavebot action (" .. currentAction.action .. "):\n" .. result) + end + else + error("Invalid cavebot action: " .. currentAction.action) + end + + if retry then + return + end + + if currentAction ~= ui.list:getFocusedChild() then + -- focused child can change durring action, get it again and reset state + currentAction = ui.list:getFocusedChild() or ui.list:getFirstChild() + actionRetries = 0 + prevActionResult = true + end + local nextAction = ui.list:getChildIndex(currentAction) + 1 + if nextAction > actions then + nextAction = 1 + end + ui.list:focusChild(ui.list:getChildByIndex(nextAction)) +end) + +-- config, its callback is called immediately, data can be nil +local lastConfig = "" +config = Config.setup("cavebot_configs", configWidget, "cfg", function(name, enabled, data) + if enabled and CaveBot.Recorder.isOn() then + CaveBot.Recorder.disable() + CaveBot.setOff() + return + end + + local currentActionIndex = ui.list:getChildIndex(ui.list:getFocusedChild()) + ui.list:destroyChildren() + if not data then return cavebotMacro.setOff() end + + local cavebotConfig = nil + for k,v in ipairs(data) do + if type(v) == "table" and #v == 2 then + if v[1] == "config" then + local status, result = pcall(function() + return json.decode(v[2]) + end) + if not status then + error("Error while parsing CaveBot extensions from config:\n" .. result) + else + cavebotConfig = result + end + elseif v[1] == "extensions" then + local status, result = pcall(function() + return json.decode(v[2]) + end) + if not status then + error("Error while parsing CaveBot extensions from config:\n" .. result) + else + for extension, callbacks in pairs(CaveBot.Extensions) do + if callbacks.onConfigChange then + callbacks.onConfigChange(name, enabled, result[extension]) + end + end + end + else + CaveBot.addAction(v[1], v[2]) + end + end + end + + CaveBot.Config.onConfigChange(name, enabled, cavebotConfig) + + actionRetries = 0 + CaveBot.resetWalking() + prevActionResult = true + cavebotMacro.setOn(enabled) + cavebotMacro.delay = nil + if lastConfig == name then + -- restore focused child on the action list + ui.list:focusChild(ui.list:getChildByIndex(currentActionIndex)) + end + lastConfig = name +end) + +-- ui callbacks +ui.showEditor.onClick = function() + if not CaveBot.Editor then return end + if ui.showEditor:isOn() then + CaveBot.Editor.hide() + ui.showEditor:setOn(false) + else + CaveBot.Editor.show() + ui.showEditor:setOn(true) + end +end + +ui.showConfig.onClick = function() + if not CaveBot.Config then return end + if ui.showConfig:isOn() then + CaveBot.Config.hide() + ui.showConfig:setOn(false) + else + CaveBot.Config.show() + ui.showConfig:setOn(true) + end +end + +-- public function, you can use them in your scripts +CaveBot.isOn = function() + return config.isOn() +end + +CaveBot.isOff = function() + return config.isOff() +end + +CaveBot.setOn = function(val) + if val == false then + return CaveBot.setOff(true) + end + config.setOn() +end + +CaveBot.setOff = function(val) + if val == false then + return CaveBot.setOn(true) + end + config.setOff() +end + +CaveBot.delay = function(value) + cavebotMacro.delay = math.max(cavebotMacro.delay or 0, now + value) +end + +CaveBot.gotoLabel = function(label) + label = label:lower() + for index, child in ipairs(ui.list:getChildren()) do + if child.action == "label" and child.value:lower() == label then + ui.list:focusChild(child) + return true + end + end + return false +end + +CaveBot.save = function() + local data = {} + for index, child in ipairs(ui.list:getChildren()) do + table.insert(data, {child.action, child.value}) + end + + if CaveBot.Config then + table.insert(data, {"config", json.encode(CaveBot.Config.save())}) + end + + local extension_data = {} + for extension, callbacks in pairs(CaveBot.Extensions) do + if callbacks.onSave then + local ext_data = callbacks.onSave() + if type(ext_data) == "table" then + extension_data[extension] = ext_data + end + end + end + table.insert(data, {"extensions", json.encode(extension_data, 2)}) + config.save(data) +end diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/cavebot.otui b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/cavebot.otui new file mode 100644 index 0000000..b92ed05 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/cavebot.otui @@ -0,0 +1,58 @@ +CaveBotAction < Label + background-color: alpha + text-offset: 2 0 + focusable: true + + $focus: + background-color: #00000055 + + +CaveBotPanel < Panel + layout: + type: verticalBox + fit-children: true + + HorizontalSeparator + margin-top: 2 + margin-bottom: 5 + + Panel + id: listPanel + height: 100 + margin-top: 2 + + TextList + id: list + anchors.fill: parent + vertical-scrollbar: listScrollbar + margin-right: 15 + focusable: false + auto-focus: first + + VerticalScrollBar + id: listScrollbar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + pixels-scroll: true + step: 10 + + BotSwitch + id: showEditor + margin-top: 2 + + $on: + text: Hide waypoints editor + + $!on: + text: Show waypoints editor + + BotSwitch + id: showConfig + margin-top: 2 + + $on: + text: Hide config + + $!on: + text: Show config \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/config.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/config.lua new file mode 100644 index 0000000..549f663 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/config.lua @@ -0,0 +1,94 @@ +-- config for bot +CaveBot.Config = {} +CaveBot.Config.values = {} +CaveBot.Config.default_values = {} +CaveBot.Config.value_setters = {} + +CaveBot.Config.setup = function() + CaveBot.Config.ui = UI.createWidget("CaveBotConfigPanel") + local ui = CaveBot.Config.ui + local add = CaveBot.Config.add + + add("ping", "Server ping", 100) + add("walkDelay", "Walk delay", 10) + add("mapClick", "Use map click", false) + add("mapClickDelay", "Map click delay", 100) + add("ignoreFields", "Ignore fields", false) + add("skipBlocked", "Skip blocked path", false) + add("useDelay", "Delay after use", 400) +end + +CaveBot.Config.show = function() + CaveBot.Config.ui:show() +end + +CaveBot.Config.hide = function() + CaveBot.Config.ui:hide() +end + +CaveBot.Config.onConfigChange = function(configName, isEnabled, configData) + for k, v in pairs(CaveBot.Config.default_values) do + CaveBot.Config.value_setters[k](v) + end + if not configData then return end + for k, v in pairs(configData) do + if CaveBot.Config.value_setters[k] then + CaveBot.Config.value_setters[k](v) + end + end +end + +CaveBot.Config.save = function() + return CaveBot.Config.values +end + +CaveBot.Config.add = function(id, title, defaultValue) + if CaveBot.Config.values[id] then + return error("Duplicated config key: " .. id) + end + + local panel + local setter -- sets value + if type(defaultValue) == "number" then + panel = UI.createWidget("CaveBotConfigNumberValuePanel", CaveBot.Config.ui) + setter = function(value) + CaveBot.Config.values[id] = value + panel.value:setText(value, true) + end + setter(defaultValue) + panel.value.onTextChange = function(widget, newValue) + newValue = tonumber(newValue) + if newValue then + CaveBot.Config.values[id] = newValue + CaveBot.save() + end + end + elseif type(defaultValue) == "boolean" then + panel = UI.createWidget("CaveBotConfigBooleanValuePanel", CaveBot.Config.ui) + setter = function(value) + CaveBot.Config.values[id] = value + panel.value:setOn(value, true) + end + setter(defaultValue) + panel.value.onClick = function(widget) + widget:setOn(not widget:isOn()) + CaveBot.Config.values[id] = widget:isOn() + CaveBot.save() + end + else + return error("Invalid default value of config for key " .. id .. ", should be number or boolean") + end + + panel.title:setText(tr(title) .. ":") + + CaveBot.Config.value_setters[id] = setter + CaveBot.Config.values[id] = defaultValue + CaveBot.Config.default_values[id] = defaultValue +end + +CaveBot.Config.get = function(id) + if CaveBot.Config.values[id] == nil then + return error("Invalid CaveBot.Config.get, id: " .. id) + end + return CaveBot.Config.values[id] +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/config.otui b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/config.otui new file mode 100644 index 0000000..21d479d --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/config.otui @@ -0,0 +1,57 @@ +CaveBotConfigPanel < Panel + id: cavebotEditor + visible: false + + layout: + type: verticalBox + fit-children: true + + HorizontalSeparator + margin-top: 5 + + Label + text-align: center + text: CaveBot Config + margin-top: 5 + +CaveBotConfigNumberValuePanel < Panel + height: 20 + margin-top: 5 + + BotTextEdit + id: value + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + margin-right: 5 + width: 50 + + Label + id: title + anchors.left: parent.left + anchors.verticalCenter: prev.verticalCenter + margin-left: 5 + +CaveBotConfigBooleanValuePanel < Panel + height: 20 + margin-top: 5 + + BotSwitch + id: value + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + margin-right: 5 + width: 50 + + $on: + text: On + + $!on: + text: Off + + Label + id: title + anchors.left: parent.left + anchors.verticalCenter: prev.verticalCenter + margin-left: 5 \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/depositer.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/depositer.lua new file mode 100644 index 0000000..d397c47 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/depositer.lua @@ -0,0 +1,27 @@ +CaveBot.Extensions.Depositer = {} + +local ui + +-- first function called, here you should setup your UI +CaveBot.Extensions.Depositer.setup = function() + --ui = UI.createWidget('Label') + --ui:setText("Depositer UI") +end + +-- called when cavebot config changes, configData is a table but it can be nil +CaveBot.Extensions.Depositer.onConfigChange = function(configName, isEnabled, configData) + if not configData then return end + +end + +-- called when cavebot is saving config, should return table or nil +CaveBot.Extensions.Depositer.onSave = function() + return {} +end + +-- bellow add you custom functions +-- this function can be used in cavebot function waypoint as: return Depositer.run(retries, prev) +-- there are 2 useful parameters - retries (number) and prev (true/false), check actions.lua to learn more +CaveBot.Extensions.Depositer.run = function(retries, prev) + return true +end diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/editor.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/editor.lua new file mode 100644 index 0000000..1fb4e76 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/editor.lua @@ -0,0 +1,174 @@ +CaveBot.Editor = {} +CaveBot.Editor.Actions = {} + +-- also works as registerAction(action, params), then text == action +-- params are options for text editor or function to be executed when clicked +-- you have many examples how to use it bellow +CaveBot.Editor.registerAction = function(action, text, params) + if type(text) ~= 'string' then + params = text + text = action + end + + local color = nil + if type(params) ~= 'function' then + local raction = CaveBot.Actions[action] + if not raction then + return error("CaveBot editor error: action " .. action .. " doesn't exist") + end + CaveBot.Editor.Actions[action] = params + color = raction.color + end + + local button = UI.createWidget('CaveBotEditorButton', CaveBot.Editor.ui.buttons) + button:setText(text) + if color then + button:setColor(color) + end + button.onClick = function() + if type(params) == 'function' then + params() + return + end + CaveBot.Editor.edit(action, nil, function(action, value) + local focusedAction = CaveBot.actionList:getFocusedChild() + local index = CaveBot.actionList:getChildCount() + if focusedAction then + index = CaveBot.actionList:getChildIndex(focusedAction) + end + local widget = CaveBot.addAction(action, value) + CaveBot.actionList:moveChildToIndex(widget, index + 1) + CaveBot.actionList:focusChild(widget) + CaveBot.save() + end) + end + return button +end + +CaveBot.Editor.setup = function() + CaveBot.Editor.ui = UI.createWidget("CaveBotEditorPanel") + local ui = CaveBot.Editor.ui + local registerAction = CaveBot.Editor.registerAction + + registerAction("move up", function() + local action = CaveBot.actionList:getFocusedChild() + if not action then return end + local index = CaveBot.actionList:getChildIndex(action) + if index < 2 then return end + CaveBot.actionList:moveChildToIndex(action, index - 1) + CaveBot.actionList:ensureChildVisible(action) + CaveBot.save() + end) + registerAction("edit", function() + local action = CaveBot.actionList:getFocusedChild() + if not action or not action.onDoubleClick then return end + action.onDoubleClick(action) + end) + registerAction("move down", function() + local action = CaveBot.actionList:getFocusedChild() + if not action then return end + local index = CaveBot.actionList:getChildIndex(action) + if index >= CaveBot.actionList:getChildCount() then return end + CaveBot.actionList:moveChildToIndex(action, index + 1) + CaveBot.actionList:ensureChildVisible(action) + CaveBot.save() + end) + registerAction("remove", function() + local action = CaveBot.actionList:getFocusedChild() + if not action then return end + action:destroy() + CaveBot.save() + end) + + registerAction("label", { + value="labelName", + title="Label", + description="Add label", + multiline=false + }) + registerAction("delay", { + value="500", + title="Delay", + description="Delay next action (in milliseconds)", + multiline=false, + validation="^\\s*[0-9]{1,10}\\s*$" + }) + registerAction("gotolabel", "go to label", { + value="labelName", + title="Go to label", + description="Go to label", + multiline=false + }) + registerAction("goto", "go to", { + value=function() return posx() .. "," .. posy() .. "," .. posz() end, + title="Go to position", + description="Go to position (x,y,z)", + multiline=false, + validation="^\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)$" + }) + registerAction("use", { + value=function() return posx() .. "," .. posy() .. "," .. posz() end, + title="Use", + description="Use item from position (x,y,z) or from inventory (itemId)", + multiline=false + }) + registerAction("usewith", "use with", { + value=function() return "itemId," .. posx() .. "," .. posy() .. "," .. posz() end, + title="Use with", + description="Use item at position (itemid,x,y,z)", + multiline=false, + validation="^\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)$" + }) + registerAction("say", { + value="text", + title="Say", + description="Enter text to say", + multiline=false + }) + registerAction("function", { + title="Edit bot function", + multiline=true, + value=CaveBot.Editor.ExampleFunctions[1][2], + examples=CaveBot.Editor.ExampleFunctions, + width=650 + }) + + ui.autoRecording.onClick = function() + if ui.autoRecording:isOn() then + CaveBot.Recorder.disable() + else + CaveBot.Recorder.enable() + end + end + + -- callbacks + onPlayerPositionChange(function(pos) + ui.pos:setText("Position: " .. pos.x .. ", " .. pos.y .. ", " .. pos.z) + end) + ui.pos:setText("Position: " .. posx() .. ", " .. posy() .. ", " .. posz()) +end + +CaveBot.Editor.show = function() + CaveBot.Editor.ui:show() +end + + +CaveBot.Editor.hide = function() + CaveBot.Editor.ui:hide() +end + +CaveBot.Editor.edit = function(action, value, callback) -- callback = function(action, value) + local params = CaveBot.Editor.Actions[action] + if not params then return end + if not value then + if type(params.value) == 'function' then + value = params.value() + elseif type(params.value) == 'string' then + value = params.value + end + end + + UI.EditorWindow(value, params, function(newText) + callback(action, newText) + end) +end diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/editor.otui b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/editor.otui new file mode 100644 index 0000000..d11288c --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/editor.otui @@ -0,0 +1,44 @@ +CaveBotEditorButton < Button + + +CaveBotEditorPanel < Panel + id: cavebotEditor + visible: false + layout: + type: verticalBox + fit-children: true + + Label + id: pos + text-align: center + text: - + + Panel + id: buttons + margin-top: 2 + layout: + type: grid + cell-size: 86 20 + cell-spacing: 1 + flow: true + fit-children: true + + Label + text: Double click on action from action list to edit it + text-align: center + text-auto-resize: true + text-wrap: true + margin-top: 3 + margin-left: 2 + margin-right: 2 + + BotSwitch + id: autoRecording + text: Auto Recording + margin-top: 3 + + BotButton + margin-top: 3 + margin-bottom: 3 + text: Documentation + @onClick: g_platform.openUrl("http://bot.otclient.ovh/") diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/example_functions.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/example_functions.lua new file mode 100644 index 0000000..556129c --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/example_functions.lua @@ -0,0 +1,90 @@ +CaveBot.Editor.ExampleFunctions = {} + +local function addExampleFunction(title, text) + return table.insert(CaveBot.Editor.ExampleFunctions, {title, text:trim()}) +end + +addExampleFunction("Click to browse example functions", [[ +-- available functions/variables: +-- prev - result of previous action (true or false) +-- retries - number of retries of current function, goes up by one when you return "retry" +-- delay(number) - delays bot next action, value in milliseconds +-- gotoLabel(string) - goes to specific label, return true if label exists +-- you can easily access bot extensions, Depositer.run() instead of CaveBot.Extensions.Depositer.run() +-- also you can access bot global variables, like CaveBot, TargetBot +-- use storage variable to store date between calls + +-- function should return false, true or "retry" +-- if "retry" is returned, function will be executed again in 20 ms (so better call delay before) + +return true +]]) + +addExampleFunction("buy 200 mana potion from npc Eryn", [[ +--buy 200 mana potions +local npc = getCreatureByName("Eryn") +if not npc then + return false +end +if retries > 10 then + return false +end +local pos = player:getPosition() +local npcPos = npc:getPosition() +if math.max(math.abs(pos.x - npcPos.x), math.abs(pos.y - npcPos.y)) > 3 then + autoWalk(npcPos, {precision=3}) + delay(300) + return "retry" +end +if not NPC.isTrading() then + NPC.say("hi") + NPC.say("trade") + delay(200) + return "retry" +end +NPC.buy(268, 100) +schedule(1000, function() + -- buy again in 1s + NPC.buy(268, 100) + NPC.closeTrade() + NPC.say("bye") +end) +delay(1200) +return true +]]) + +addExampleFunction("Say hello 5 times with some delay", [[ +--say hello +if retries > 5 then + return true -- finish +end +say("hello") +delay(100 + retries * 100) +return "retry" +]]) + +addExampleFunction("Disable TargetBot", [[ +TargetBot.setOff() +return true +]]) + +addExampleFunction("Enable TargetBot", [[ +TargetBot.setOn() +return true +]]) + +addExampleFunction("Enable TargetBot luring", [[ +TargetBot.enableLuring() +return true +]]) + +addExampleFunction("Disable TargetBot luring", [[ +TargetBot.disableLuring() +return true +]]) + +addExampleFunction("Logout", [[ +g_game.safeLogout() +delay(1000) +return "retry" +]]) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/extension_template.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/extension_template.lua new file mode 100644 index 0000000..d015f11 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/extension_template.lua @@ -0,0 +1,58 @@ +-- example cavebot extension (remember to add this file to ../cavebot.lua) +CaveBot.Extensions.Example = {} + +local ui + +-- setup is called automaticly when cavebot is ready +CaveBot.Extensions.Example.setup = function() + ui = UI.createWidget('BotTextEdit') + ui:setText("Hello") + ui.onTextChange = function() + CaveBot.save() -- save new config when you change something + end + + -- add custom cavebot action (check out actions.lua) + CaveBot.registerAction("sayhello", "orange", function(value, retries, prev) + local how_many_times = tonumber(value) + if retries >= how_many_times then + return true + end + say("hello " .. (retries + 1)) + delay(250) + return "retry" + end) + + -- add this custom action to editor (check out editor.lua) + CaveBot.Editor.registerAction("sayhello", "say hello", { + value="5", + title="Say hello", + description="Says hello x times", + validation="[0-9]{1,5}" -- regex, optional + }) +end + +-- called when cavebot config changes, configData is a table but it can also be nil +CaveBot.Extensions.Example.onConfigChange = function(configName, isEnabled, configData) + if not configData then return end + if configData["text"] then + ui:setText(configData["text"]) + end +end + +-- called when cavebot is saving config (so when CaveBot.save() is called), should return table or nil +CaveBot.Extensions.Example.onSave = function() + return {text=ui:getText()} +end + +-- bellow add you custom functions to be used in cavebot function action +-- an example: return Example.run(retries, prev) +-- there are 2 useful parameters - retries (number) and prev (true/false), check actions.lua and example_functions.lua to learn more +CaveBot.Extensions.Example.run = function(retries, prev) + -- it will say text 10 times with some delay and then continue + if retries > 10 then + return true + end + say(ui:getText() .. " x" .. retries) + delay(100 + retries * 100) + return "retry" +end diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/recorder.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/recorder.lua new file mode 100644 index 0000000..27206ba --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/recorder.lua @@ -0,0 +1,65 @@ +-- auto recording for cavebot +CaveBot.Recorder = {} + +local isEnabled = nil +local lastPos = nil + +local function setup() + local function addPosition(pos) + CaveBot.addAction("goto", pos.x .. "," .. pos.y .. "," .. pos.z, true) + lastPos = pos + end + + onPlayerPositionChange(function(newPos, oldPos) + if CaveBot.isOn() or not isEnabled then return end + if not lastPos then + -- first step + addPosition(oldPos) + elseif newPos.z ~= oldPos.z or math.abs(oldPos.x - newPos.x) > 1 or math.abs(oldPos.y - newPos.y) > 1 then + -- stairs/teleport + addPosition(oldPos) + elseif math.max(math.abs(lastPos.x - newPos.x), math.abs(lastPos.y - newPos.y)) > 5 then + -- 5 steps from last pos + addPosition(newPos) + end + end) + + onUse(function(pos, itemId, stackPos, subType) + if CaveBot.isOn() or not isEnabled then return end + if pos.x ~= 0xFFFF then + lastPos = pos + CaveBot.addAction("use", pos.x .. "," .. pos.y .. "," .. pos.z, true) + end + end) + + onUseWith(function(pos, itemId, target, subType) + if CaveBot.isOn() or not isEnabled then return end + if not target:isItem() then return end + local targetPos = target:getPosition() + if targetPos.x == 0xFFFF then return end + lastPos = pos + CaveBot.addAction("usewith", itemId .. "," .. targetPos.x .. "," .. targetPos.y .. "," .. targetPos.z, true) + end) +end + +CaveBot.Recorder.isOn = function() + return isEnabled +end + +CaveBot.Recorder.enable = function() + CaveBot.setOff() + if isEnabled == nil then + setup() + end + CaveBot.Editor.ui.autoRecording:setOn(true) + isEnabled = true + lastPos = nil +end + +CaveBot.Recorder.disable = function() + if isEnabled == true then + isEnabled = false + end + CaveBot.Editor.ui.autoRecording:setOn(false) + CaveBot.save() +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/supply.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/supply.lua new file mode 100644 index 0000000..b3cd4ca --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/supply.lua @@ -0,0 +1,30 @@ +CaveBot.Extensions.Supply = {} + +local ui + +-- first function called, here you should setup your UI +CaveBot.Extensions.Supply.setup = function() + --ui = UI.createWidget('SupplyItemList') + --local widget = UI.createWidget('SupplyItem', ui.list) + --widget.item.onItemChange = function(newItem) + --widget.fields.min.onTextChange = function(newText) + -- make it similar to UI.Container, so if there are no free slots, add another one, keep min 4 slots, check if value min/max is number after edit +end + +-- called when cavebot config changes, configData is a table but it can be nil +CaveBot.Extensions.Supply.onConfigChange = function(configName, isEnabled, configData) + if not configData then return end + +end + +-- called when cavebot is saving config, should return table or nil +CaveBot.Extensions.Supply.onSave = function() + return {} +end + +-- bellow add you custom functions +-- this function can be used in cavebot function waypoint as: return Supply.run(retries, prev) +-- there are 2 useful parameters - retries (number) and prev (true/false), check actions.lua to learn more +CaveBot.Extensions.Supply.run = function(retries, prev) + return true +end diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/supply.otui b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/supply.otui new file mode 100644 index 0000000..83c76ac --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/supply.otui @@ -0,0 +1,72 @@ +SupplyItem < Panel + height: 34 + + BotItem + id: item + size: 32 32 + anchors.left: parent.left + anchors.top: parent.top + margin-top: 1 + + Panel + id: fields + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: prev.right + anchors.right: parent.right + margin-left: 2 + margin-right: 2 + + Label + id: minLabel + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.horizontalCenter + margin-right: 2 + text-align: center + text: "Min" + + Label + id: maxLabel + anchors.top: parent.top + anchors.left: parent.horizontalCenter + anchors.right: parent.right + margin-left: 2 + text-align: center + text: "Max" + + BotTextEdit + id: min + anchors.top: minLabel.bottom + anchors.left: minLabel.left + anchors.right: minLabel.right + text-align: center + text: 1 + + BotTextEdit + id: max + anchors.top: maxLabel.bottom + anchors.left: maxLabel.left + anchors.right: maxLabel.right + text-align: center + text: 100 + +SupplyItemList < Panel + height: 102 + + ScrollablePanel + id: list + anchors.fill: parent + vertical-scrollbar: scroll + margin-right: 7 + layout: + type: verticalBox + cell-height: 34 + + BotSmallScrollBar + id: scroll + anchors.top: prev.top + anchors.bottom: prev.bottom + anchors.right: parent.right + step: 10 + pixels-scroll: true diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/walking.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/walking.lua new file mode 100644 index 0000000..c8a7133 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot/walking.lua @@ -0,0 +1,93 @@ +-- walking +local expectedDirs = {} +local isWalking = {} +local walkPath = {} +local walkPathIter = 0 + +CaveBot.resetWalking = function() + expectedDirs = {} + walkPath = {} + isWalking = false +end + +CaveBot.doWalking = function() + if CaveBot.Config.get("mapClick") then + return false + end + if #expectedDirs == 0 then + return false + end + if #expectedDirs >= 3 then + CaveBot.resetWalking() + end + local dir = walkPath[walkPathIter] + if dir then + g_game.walk(dir, false) + table.insert(expectedDirs, dir) + walkPathIter = walkPathIter + 1 + CaveBot.delay(CaveBot.Config.get("walkDelay") + player:getStepDuration(false, dir)) + return true + end + return false +end + +-- called when player position has been changed (step has been confirmed by server) +onPlayerPositionChange(function(newPos, oldPos) + if not oldPos or not newPos then return end + + local dirs = {{NorthWest, North, NorthEast}, {West, 8, East}, {SouthWest, South, SouthEast}} + local dir = dirs[newPos.y - oldPos.y + 2] + if dir then + dir = dir[newPos.x - oldPos.x + 2] + end + if not dir then + dir = 8 -- 8 is invalid dir, it's fine + end + + if not isWalking or not expectedDirs[1] then + -- some other walk action is taking place (for example use on ladder), wait + walkPath = {} + CaveBot.delay(CaveBot.Config.get("ping") + player:getStepDuration(false, dir) + 150) + return + end + + if expectedDirs[1] ~= dir then + if CaveBot.Config.get("mapClick") then + CaveBot.delay(CaveBot.Config.get("walkDelay") + player:getStepDuration(false, dir)) + else + CaveBot.delay(CaveBot.Config.get("mapClickDelay") + player:getStepDuration(false, dir)) + end + return + end + + table.remove(expectedDirs, 1) + if CaveBot.Config.get("mapClick") and #expectedDirs > 0 then + CaveBot.delay(CaveBot.Config.get("mapClickDelay") + player:getStepDuration(false, dir)) + end +end) + +CaveBot.walkTo = function(dest, maxDist, params) + local path = getPath(player:getPosition(), dest, maxDist, params) + if not path or not path[1] then + return false + end + local dir = path[1] + + if CaveBot.Config.get("mapClick") then + local ret = autoWalk(path) + if ret then + isWalking = true + expectedDirs = path + CaveBot.delay(CaveBot.Config.get("mapClickDelay") + math.max(CaveBot.Config.get("ping") + player:getStepDuration(false, dir), player:getStepDuration(false, dir) * 2)) + end + return ret + end + + g_game.walk(dir, false) + isWalking = true + walkPath = path + walkPathIter = 2 + expectedDirs = { dir } + CaveBot.delay(CaveBot.Config.get("walkDelay") + player:getStepDuration(false, dir)) + return true +end diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot_configs/config_name.cfg b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot_configs/config_name.cfg new file mode 100644 index 0000000..abff9d8 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot_configs/config_name.cfg @@ -0,0 +1,23 @@ +goto:1033,1044,7 +goto:1031,1038,7 +goto:1030,1038,7 +goto:1157,985,7 +goto:1161,981,7 +goto:1033,1042,7 +goto:1034,1038,7 +goto:1169,985,7 +goto:1175,985,7 +goto:1176,983,7 +goto:756,846,7 +goto:756,846,7 +config:{"walk":100,"walk2":false} +extensions:[[ +{ + "Depositer": [ + + ], + "Supply": [ + + ] +} +]] diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot_configs/fast_walking.cfg b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot_configs/fast_walking.cfg new file mode 100644 index 0000000..a5bb130 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot_configs/fast_walking.cfg @@ -0,0 +1,13 @@ +goto:84,112,6 +goto:95,108,6 +config:{"mapClickDelay":100,"walkDelay":10,"ping":250,"ignoreFields":false,"useDelay":400,"mapClick":false} +extensions:[[ +{ + "Depositer": [ + + ], + "Supply": [ + + ] +} +]] diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot_configs/test_src.cfg b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot_configs/test_src.cfg new file mode 100644 index 0000000..1049ce7 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/cavebot_configs/test_src.cfg @@ -0,0 +1,104 @@ +goto:93,129,7 +goto:96,123,7 +goto:96,117,7 +goto:101,114,7 +goto:95,111,6 +goto:89,111,6 +goto:83,108,6 +goto:80,102,6 +goto:80,96,6 +goto:85,90,6 +goto:88,92,6 +goto:91,86,7 +goto:97,85,7 +goto:103,84,7 +function:[[ + +TargetBot.enableLuring() + +return true + + +]] +goto:109,79,7 +goto:112,79,7 +goto:112,79,8 +function:[[ + +TargetBot.disableLuring() + +return true + + +]] +goto:112,79,7 +goto:106,84,8 +goto:100,80,8 +goto:100,74,8 +goto:99,80,8 +goto:105,83,8 +function:[[ +TargetBot.setOff() +return true +]] +goto:111,82,8 +goto:112,79,8 +goto:106,82,7 +goto:100,85,7 +goto:94,85,7 +goto:91,91,7 +goto:89,92,7 +goto:83,90,6 +function:[[ +TargetBot.setOff() +return true +]] +goto:77,94,6 +goto:75,95,6 +goto:69,96,7 +goto:63,100,7 +goto:61,102,7 +goto:62,96,8 +use:61,102,8 +goto:62,101,8 +goto:68,99,7 +goto:74,95,7 +goto:75,95,7 +goto:79,101,6 +goto:81,107,6 +goto:87,109,6 +goto:93,112,6 +function:[[ + +TargetBot.disableLuring() + +return true + + +]] +goto:99,116,6 +use:102,114,6 +goto:101,115,6 +use:100,116,5 +goto:101,115,5 +goto:100,116,4 +goto:102,114,5 +goto:101,114,6 +goto:96,120,7 +goto:95,126,7 +function:[[ +g_game.safeLogout() +delay(1000) +return "retry" +]] +config:{"useDelay":400,"mapClickDelay":100,"walkDelay":20,"ping":150,"ignoreFields":false,"skipBlocked":true,"mapClick":false} +extensions:[[ +{ + "Depositer": [ + + ], + "Supply": [ + + ] +} +]] diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/hp.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/hp.lua new file mode 100644 index 0000000..88b44d1 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/hp.lua @@ -0,0 +1,192 @@ +setDefaultTab("HP") + +--2x healing spell +--2x healing rune +--utani hur +--mana shield +--anti paralyze +--4x equip + +UI.Label("Healing spells") + +if type(storage.healing1) ~= "table" then + storage.healing1 = {on=false, title="HP%", text="exura", min=51, max=90} +end +if type(storage.healing2) ~= "table" then + storage.healing2 = {on=false, title="HP%", text="exura vita", min=0, max=50} +end + +-- create 2 healing widgets +for _, healingInfo in ipairs({storage.healing1, storage.healing2}) do + local healingmacro = macro(20, function() + local hp = player:getHealthPercent() + if healingInfo.max >= hp and hp >= healingInfo.min then + if TargetBot then + TargetBot.saySpell(healingInfo.text) -- sync spell with targetbot if available + else + say(healingInfo.text) + end + end + end) + healingmacro.setOn(healingInfo.on) + + UI.DualScrollPanel(healingInfo, function(widget, newParams) + healingInfo = newParams + healingmacro.setOn(healingInfo.on) + end) +end + +UI.Separator() + +UI.Label("Mana & health potions/runes") + +if type(storage.hpitem1) ~= "table" then + storage.hpitem1 = {on=false, title="HP%", item=266, min=51, max=90} +end +if type(storage.hpitem2) ~= "table" then + storage.hpitem2 = {on=false, title="HP%", item=3160, min=0, max=50} +end +if type(storage.manaitem1) ~= "table" then + storage.manaitem1 = {on=false, title="MP%", item=268, min=51, max=90} +end +if type(storage.manaitem2) ~= "table" then + storage.manaitem2 = {on=false, title="MP%", item=3157, min=0, max=50} +end + +for i, healingInfo in ipairs({storage.hpitem1, storage.hpitem2, storage.manaitem1, storage.manaitem2}) do + local healingmacro = macro(20, function() + local hp = i <= 2 and player:getHealthPercent() or math.min(100, math.floor(100 * (player:getMana() / player:getMaxMana()))) + if healingInfo.max >= hp and hp >= healingInfo.min then + if TargetBot then + TargetBot.useItem(healingInfo.item, healingInfo.subType, player) -- sync spell with targetbot if available + else + local thing = g_things.getThingType(healingInfo.item) + local subType = g_game.getClientVersion() >= 860 and 0 or 1 + if thing and thing:isFluidContainer() then + subType = healingInfo.subType + end + g_game.useInventoryItemWith(healingInfo.item, player, subType) + end + end + end) + healingmacro.setOn(healingInfo.on) + + UI.DualScrollItemPanel(healingInfo, function(widget, newParams) + healingInfo = newParams + healingmacro.setOn(healingInfo.on and healingInfo.item > 100) + end) +end + +if g_game.getClientVersion() < 780 then + UI.Label("In old tibia potions & runes work only when you have backpack with them opened") +end + +UI.Separator() + +UI.Label("Mana shield spell:") +UI.TextEdit(storage.manaShield or "utamo vita", function(widget, newText) + storage.manaShield = newText +end) + +local lastManaShield = 0 +macro(20, "mana shield", function() + if hasManaShield() or lastManaShield + 90000 > now then return end + if TargetBot then + TargetBot.saySpell(storage.manaShield) -- sync spell with targetbot if available + else + say(storage.manaShield) + end +end) + +UI.Label("Haste spell:") +UI.TextEdit(storage.hasteSpell or "utani hur", function(widget, newText) + storage.hasteSpell = newText +end) + +macro(500, "haste", function() + if hasHaste() then return end + if TargetBot then + TargetBot.saySpell(storage.hasteSpell) -- sync spell with targetbot if available + else + say(storage.hasteSpell) + end +end) + +UI.Label("Anti paralyze spell:") +UI.TextEdit(storage.antiParalyze or "utani hur", function(widget, newText) + storage.antiParalyze = newText +end) + +macro(100, "anti paralyze", function() + if not isParalyzed() then return end + if TargetBot then + TargetBot.saySpell(storage.antiParalyze) -- sync spell with targetbot if available + else + say(storage.antiParalyze) + end +end) + +UI.Separator() + +UI.Label("Eatable items:") +if type(storage.foodItems) ~= "table" then + storage.foodItems = {3582, 3577} +end + +local foodContainer = UI.Container(function(widget, items) + storage.foodItems = items +end, true) +foodContainer:setHeight(35) +foodContainer:setItems(storage.foodItems) + +macro(10000, "eat food", function() + if not storage.foodItems[1] then return end + -- search for food in containers + for _, container in pairs(g_game.getContainers()) do + for __, item in ipairs(container:getItems()) do + for i, foodItem in ipairs(storage.foodItems) do + if item:getId() == foodItem.id then + return g_game.use(item) + end + end + end + end + -- can't find any food, try to eat random item using hotkey + if g_game.getClientVersion() < 780 then return end -- hotkey's dont work on old tibia + local toEat = storage.foodItems[math.random(1, #storage.foodItems)] + if toEat then g_game.useInventoryItem(toEat.id) end +end) + +UI.Separator() +UI.Label("Auto equip") + +if type(storage.autoEquip) ~= "table" then + storage.autoEquip = {} +end +for i=1,4 do -- if you want more auto equip panels you can change 4 to higher value + if not storage.autoEquip[i] then + storage.autoEquip[i] = {on=false, title="Auto Equip", item1=i == 1 and 3052 or 0, item2=i == 1 and 3089 or 0, slot=i == 1 and 9 or 0} + end + UI.TwoItemsAndSlotPanel(storage.autoEquip[i], function(widget, newParams) + storage.autoEquip[i] = newParams + end) +end +macro(250, function() + local containers = g_game.getContainers() + for index, autoEquip in ipairs(storage.autoEquip) do + if autoEquip.on then + local slotItem = getSlot(autoEquip.slot) + if not slotItem or (slotItem:getId() ~= autoEquip.item1 and slotItem:getId() ~= autoEquip.item2) then + for _, container in pairs(containers) do + for __, item in ipairs(container:getItems()) do + if item:getId() == autoEquip.item1 or item:getId() == autoEquip.item2 then + g_game.move(item, {x=65535, y=autoEquip.slot, z=0}, item:getCount()) + delay(1000) -- don't call it too often + return + end + end + end + end + end + end +end) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/main.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/main.lua new file mode 100644 index 0000000..57b7ca6 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/main.lua @@ -0,0 +1,22 @@ +-- main tab +VERSION = "1.3" + +UI.Label("Config version: " .. VERSION) + +UI.Separator() + + + +UI.Separator() + +UI.Button("Discord", function() + g_platform.openUrl("https://discord.gg/yhqBE4A") +end) + +UI.Button("Forum", function() + g_platform.openUrl("http://otclient.net/") +end) + +UI.Button("Help & Tutorials", function() + g_platform.openUrl("http://bot.otclient.ovh/") +end) diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/mwall_timer.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/mwall_timer.lua new file mode 100644 index 0000000..6dc1ec4 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/mwall_timer.lua @@ -0,0 +1,41 @@ +-- Magic wall & Wild growth timer + +-- config +local magicWallId = 2129 +local magicWallTime = 20000 +local wildGrowthId = 2130 +local wildGrowthTime = 45000 + +-- script +local activeTimers = {} + +onAddThing(function(tile, thing) + if not thing:isItem() then + return + end + local timer = 0 + if thing:getId() == magicWallId then + timer = magicWallTime + elseif thing:getId() == wildGrowthId then + timer = wildGrowthTime + else + return + end + + local pos = tile:getPosition().x .. "," .. tile:getPosition().y .. "," .. tile:getPosition().z + if not activeTimers[pos] or activeTimers[pos] < now then + activeTimers[pos] = now + timer + end + tile:setTimer(activeTimers[pos] - now) +end) + +onRemoveThing(function(tile, thing) + if not thing:isItem() then + return + end + if (thing:getId() == magicWallId or thing:getId() == wildGrowthId) and tile:getGround() then + local pos = tile:getPosition().x .. "," .. tile:getPosition().y .. "," .. tile:getPosition().z + activeTimers[pos] = nil + tile:setTimer(0) + end +end) diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/storage.json b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/storage.json new file mode 100644 index 0000000..afd37c9 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/storage.json @@ -0,0 +1,128 @@ +{ + "hpitem1": { + "max": 90, + "title": "HP%", + "subType": 0, + "item": 266, + "min": 51, + "on": false + }, + "foodItems": [ + { + "id": 3582, + "count": 1 + }, + { + "id": 3577, + "count": 1 + } + ], + "autoEquip": [ + { + "item1": 3052, + "title": "Auto Equip", + "item2": 3089, + "on": false, + "slot": 9 + }, + { + "item1": 0, + "title": "Auto Equip", + "item2": 0, + "on": false, + "slot": 0 + }, + { + "item1": 0, + "title": "Auto Equip", + "item2": 0, + "on": false, + "slot": 0 + }, + { + "item1": 0, + "title": "Auto Equip", + "item2": 0, + "on": false, + "slot": 0 + } + ], + "ingame_hotkeys": "singlehotkey(\"f1\", function()\nlocal shaders = {\"stars\", \"gold\", \"rainbow\", \"sweden\", \"brazil\", \"line\", \"3line\", \"circle\", \"outline\"}\nlocal p = 0\nfor i, c in pairs(getSpectators()) do\n c:setOutfitShader(shaders[1 + p % 10])\n p = p + 1\nend\nend)\n\nsinglehotkey(\"1\", function()\n for _, s in ipairs(getSpectators()) do\n if s:canShoot(3) then\n info(s:getName())\n else\n warn(s:getName())\n end\n end\nend)", + "healing2": { + "max": 50, + "title": "HP%", + "on": false, + "min": 1, + "text": "exura vita" + }, + "ingame_macros": "", + "hasteSpell": "utani hur", + "manaitem2": { + "max": 50, + "title": "MP%", + "subType": 0, + "item": 3157, + "min": 0, + "on": false + }, + "_configs": { + "cavebot_configs": { + "selected": "test_src", + "enabled": false + }, + "targetbot_configs": { + "enabled": false, + "selected": "config_name" + } + }, + "healing1": { + "max": 100, + "title": "HP%", + "on": false, + "min": 51, + "text": "exura" + }, + "dropItems": [ + { + "id": 283, + "count": 1 + }, + { + "id": 284, + "count": 1 + }, + { + "id": 285, + "count": 1 + } + ], + "_macros": { + "": false + }, + "manaitem1": { + "max": 90, + "title": "MP%", + "subType": 0, + "item": 268, + "min": 51, + "on": false + }, + "hpitem2": { + "max": 50, + "title": "HP%", + "subType": 0, + "item": 3160, + "min": 0, + "on": false + }, + "manaShield": "utamo vita", + "autoTradeMessage": "I'm using OTClientV8!", + "antiParalyze": "utani hur", + "manaTrain": { + "max": 100, + "title": "MP%", + "on": false, + "min": 80, + "text": "utevo lux" + } +} \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/creature.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/creature.lua new file mode 100644 index 0000000..d4dd545 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/creature.lua @@ -0,0 +1,99 @@ + +TargetBot.Creature = {} +TargetBot.Creature.configsCache = {} +TargetBot.Creature.cached = 0 + +TargetBot.Creature.resetConfigs = function() + TargetBot.targetList:destroyChildren() + TargetBot.Creature.resetConfigsCache() +end + +TargetBot.Creature.resetConfigsCache = function() + TargetBot.Creature.configsCache = {} + TargetBot.Creature.cached = 0 +end + +TargetBot.Creature.addConfig = function(config, focus) + if type(config) ~= 'table' or type(config.name) ~= 'string' then + return error("Invalid targetbot creature config (missing name)") + end + TargetBot.Creature.resetConfigsCache() + + if not config.regex then + config.regex = "" + for part in string.gmatch(config.name, "[^,]+") do + if config.regex:len() > 0 then + config.regex = config.regex .. "|" + end + config.regex = config.regex .. "^" .. part:trim():lower():gsub("%*", ".*"):gsub("%?", ".?") .. "$" + end + end + + local widget = UI.createWidget("TargetBotEntry", TargetBot.targetList) + widget:setText(config.name) + widget.value = config + + widget.onDoubleClick = function(entry) -- edit on double click + schedule(20, function() -- schedule to have correct focus + TargetBot.Creature.edit(entry.value, function(newConfig) + entry:setText(newConfig.name) + entry.value = newConfig + TargetBot.Creature.resetConfigsCache() + TargetBot.save() + end) + end) + end + + if focus then + widget:focus() + TargetBot.targetList:ensureChildVisible(widget) + end + return widget +end + +TargetBot.Creature.getConfigs = function(creature) + if not creature then return {} end + local name = creature:getName():trim():lower() + -- this function may be slow, so it will be using cache + if TargetBot.Creature.configsCache[name] then + return TargetBot.Creature.configsCache[name] + end + local configs = {} + for _, config in ipairs(TargetBot.targetList:getChildren()) do + if regexMatch(name, config.value.regex)[1] then + table.insert(configs, config.value) + end + end + if TargetBot.Creature.cached > 1000 then + TargetBot.Creature.resetConfigsCache() -- too big cache size, reset + end + TargetBot.Creature.configsCache[name] = configs -- add to cache + TargetBot.Creature.cached = TargetBot.Creature.cached + 1 + return configs +end + +TargetBot.Creature.calculateParams = function(creature, path) + local configs = TargetBot.Creature.getConfigs(creature) + local priority = 0 + local danger = 0 + local selectedConfig = nil + for _, config in ipairs(configs) do + local config_priority = TargetBot.Creature.calculatePriority(creature, config, path) + if config_priority > priority then + priority = config_priority + danger = TargetBot.Creature.calculateDanger(creature, config, path) + selectedConfig = config + end + end + return { + config = selectedConfig, + creature = creature, + danger = danger, + priority = priority + } +end + +TargetBot.Creature.calculateDanger = function(creature, config, path) + -- config is based on creature_editor + return config.danger +end diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/creature_attack.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/creature_attack.lua new file mode 100644 index 0000000..048c07c --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/creature_attack.lua @@ -0,0 +1,122 @@ +TargetBot.Creature.attack = function(params, targets, isLooting) -- params {config, creature, danger, priority} + if player:isWalking() then + lastWalk = now + end + + local config = params.config + local creature = params.creature + + if g_game.getAttackingCreature() ~= creature then + g_game.attack(creature) + end + + if not isLooting then -- walk only when not looting + TargetBot.Creature.walk(creature, config, targets) + end + + -- attacks + local mana = player:getMana() + if config.useGroupAttack and config.groupAttackSpell:len() > 1 and mana > config.minManaGroup then + local creatures = g_map.getSpectatorsInRange(player:getPosition(), false, config.groupAttackRadius, config.groupAttackRadius) + local playersAround = false + local monsters = 0 + for _, creature in ipairs(creatures) do + if not creature:isLocalPlayer() and creature:isPlayer() and (not config.groupAttackIgnoreParty or creature:getShield() <= 2) then + playersAround = true + elseif creature:isMonster() then + monsters = monsters + 1 + end + end + if monsters >= config.groupAttackTargets and (not playersAround or config.groupAttackIgnorePlayers) then + if TargetBot.sayAttackSpell(config.groupAttackSpell, config.groupAttackDelay) then + return + end + end + end + + if config.useGroupAttackRune and config.groupAttackRune > 100 then + local creatures = g_map.getSpectatorsInRange(creature:getPosition(), false, config.groupRuneAttackRadius, config.groupRuneAttackRadius) + local playersAround = false + local monsters = 0 + for _, creature in ipairs(creatures) do + if not creature:isLocalPlayer() and creature:isPlayer() and (not config.groupAttackIgnoreParty or creature:getShield() <= 2) then + playersAround = true + elseif creature:isMonster() then + monsters = monsters + 1 + end + end + if monsters >= config.groupRuneAttackTargets and (not playersAround or config.groupAttackIgnorePlayers) then + if TargetBot.useAttackItem(config.groupAttackRune, 0, creature, config.groupRuneAttackDelay) then + return + end + end + end + if config.useSpellAttack and config.attackSpell:len() > 1 and mana > config.minMana then + if TargetBot.sayAttackSpell(config.attackSpell, config.attackSpellDelay) then + return + end + end + if config.useRuneAttack and config.attackRune > 100 then + if TargetBot.useAttackItem(config.attackRune, 0, creature, config.attackRuneDelay) then + return + end + end +end + +TargetBot.Creature.walk = function(creature, config, targets) + local cpos = creature:getPosition() + local pos = player:getPosition() + + local isTrapped = true + local pos = player:getPosition() + local dirs = {{-1,1}, {0,1}, {1,1}, {-1, 0}, {1, 0}, {-1, -1}, {0, -1}, {1, -1}} + for i=1,#dirs do + local tile = g_map.getTile({x=pos.x-dirs[i][1],y=pos.y-dirs[i][2],z=pos.z}) + if tile and tile:isWalkable(false) then + isTrapped = false + end + end + + -- luring + if TargetBot.canLure() and (config.lure or config.lureCavebot) and not (config.chase and creature:getHealthPercent() < 30) and not isTrapped then + local monsters = 0 + if targets < config.lureCount then + if config.lureCavebot then + return TargetBot.allowCaveBot(200) + else + local path = findPath(pos, cpos, 5, {ignoreNonPathable=true, precision=2}) + if path then + return TargetBot.walkTo(cpos, 10, {marginMin=5, marginMax=6, ignoreNonPathable=true}) + end + end + end + end + + local currentDistance = findPath(pos, cpos, 10, {ignoreCreatures=true, ignoreNonPathable=true, ignoreCost=true}) + if config.chase and (creature:getHealthPercent() < 30 or not config.keepDistance) then + if #currentDistance > 1 then + return TargetBot.walkTo(cpos, 10, {ignoreNonPathable=true, precision=1}) + end + elseif config.keepDistance then + if #currentDistance ~= config.keepDistanceRange and #currentDistance ~= config.keepDistanceRange + 1 then + return TargetBot.walkTo(cpos, 10, {ignoreNonPathable=true, marginMin=config.keepDistanceRange, marginMax=config.keepDistanceRange + 1}) + end + end + + if config.avoidAttacks then + local diffx = cpos.x - pos.x + local diffy = cpos.y - pos.y + local candidates = {} + if math.abs(diffx) == 1 and diffy == 0 then + candidates = {{x=pos.x, y=pos.y-1, z=pos.z}, {x=pos.x, y=pos.y+1, z=pos.z}} + elseif diffx == 0 and math.abs(diffy) == 1 then + candidates = {{x=pos.x-1, y=pos.y, z=pos.z}, {x=pos.x+1, y=pos.y, z=pos.z}} + end + for _, candidate in ipairs(candidates) do + local tile = g_map.getTile(candidate) + if tile and tile:isWalkable() then + return TargetBot.walkTo(candidate, 2, {ignoreNonPathable=true}) + end + end + end +end diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/creature_editor.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/creature_editor.lua new file mode 100644 index 0000000..8d92db0 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/creature_editor.lua @@ -0,0 +1,113 @@ +TargetBot.Creature.edit = function(config, callback) -- callback = function(newConfig) + config = config or {} + + local editor = UI.createWindow('TargetBotCreatureEditorWindow') + local values = {} -- (key, function returning value of key) + + editor.name:setText(config.name or "") + table.insert(values, {"name", function() return editor.name:getText() end}) + + local addScrollBar = function(id, title, min, max, defaultValue) + local widget = UI.createWidget('TargetBotCreatureEditorScrollBar', editor.content.left) + widget.scroll.onValueChange = function(scroll, value) + widget.text:setText(title .. ": " .. value) + end + widget.scroll:setRange(min, max) + if max-min > 1000 then + widget.scroll:setStep(100) + elseif max-min > 100 then + widget.scroll:setStep(10) + end + widget.scroll:setValue(config[id] or defaultValue) + widget.scroll.onValueChange(widget.scroll, widget.scroll:getValue()) + table.insert(values, {id, function() return widget.scroll:getValue() end}) + end + + local addTextEdit = function(id, title, defaultValue) + local widget = UI.createWidget('TargetBotCreatureEditorTextEdit', editor.content.right) + widget.text:setText(title) + widget.textEdit:setText(config[id] or defaultValue or "") + table.insert(values, {id, function() return widget.textEdit:getText() end}) + end + + local addCheckBox = function(id, title, defaultValue) + local widget = UI.createWidget('TargetBotCreatureEditorCheckBox', editor.content.right) + widget.onClick = function() + widget:setOn(not widget:isOn()) + end + widget:setText(title) + if config[id] == nil then + widget:setOn(defaultValue) + else + widget:setOn(config[id]) + end + table.insert(values, {id, function() return widget:isOn() end}) + end + + local addItem = function(id, title, defaultItem) + local widget = UI.createWidget('TargetBotCreatureEditorItem', editor.content.right) + widget.text:setText(title) + widget.item:setItemId(config[id] or defaultItem) + table.insert(values, {id, function() return widget.item:getItemId() end}) + end + + editor.cancel.onClick = function() + editor:destroy() + end + editor.onEscape = editor.cancel.onClick + + editor.ok.onClick = function() + local newConfig = {} + for _, value in ipairs(values) do + newConfig[value[1]] = value[2]() + end + if newConfig.name:len() < 1 then return end + + newConfig.regex = "" + for part in string.gmatch(newConfig.name, "[^,]+") do + if newConfig.regex:len() > 0 then + newConfig.regex = newConfig.regex .. "|" + end + newConfig.regex = newConfig.regex .. "^" .. part:trim():lower():gsub("%*", ".*"):gsub("%?", ".?") .. "$" + end + + editor:destroy() + callback(newConfig) + end + + -- values + addScrollBar("priority", "Priority", 0, 10, 1) + addScrollBar("danger", "Danger", 0, 10, 1) + addScrollBar("maxDistance", "Max distance", 1, 10, 10) + addScrollBar("keepDistanceRange", "Keep distance", 1, 5, 1) + addScrollBar("lureCount", "Lure", 0, 5, 1) + + addScrollBar("minMana", "Min. mana for attack spell", 0, 3000, 200) + addScrollBar("attackSpellDelay", "Attack spell delay", 200, 5000, 2500) + addScrollBar("minManaGroup", "Min. mana for group attack", 0, 3000, 1500) + addScrollBar("groupAttackTargets", "Min. targets for group attack", 1, 10, 2) + addScrollBar("groupAttackRadius", "Radius of group attack spell", 1, 7, 1) + addScrollBar("groupAttackDelay", "Group attack spell delay", 200, 60000, 5000) + addScrollBar("runeAttackDelay", "Rune attack delay", 200, 5000, 2000) + addScrollBar("groupRuneAttackTargets", "Min. targets for group rune attack", 1, 10, 2) + addScrollBar("groupRuneAttackRadius", "Radius of group rune attack", 1, 7, 1) + addScrollBar("groupRuneAttackDelay", "Group rune attack delay", 200, 60000, 5000) + + addCheckBox("chase", "Chase", true) + addCheckBox("keepDistance", "Keep Distance", false) + addCheckBox("dontLoot", "Don't loot", false) + addCheckBox("lure", "Lure", false) + addCheckBox("lureCavebot", "Lure using cavebot", false) + addCheckBox("avoidAttacks", "Avoid wave attacks", false) + + addCheckBox("useSpellAttack", "Use attack spell", false) + addTextEdit("attackSpell", "Attack spell", "") + addCheckBox("useRuneAttack", "Use attack rune", false) + addItem("attackRune", "Attack rune:", 0) + addCheckBox("useGroupAttack", "Use group attack spell", false) + addTextEdit("groupAttackSpell", "Group attack spell", "") + addCheckBox("useGroupAttackRune", "Use group attack rune", false) + addItem("groupAttackRune", "Group attack rune:", 0) + addCheckBox("groupAttackIgnorePlayers", "Ignore players in group attack", false) + addCheckBox("groupAttackIgnoreParty", "Ignore party in group attack", false) +end diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/creature_editor.otui b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/creature_editor.otui new file mode 100644 index 0000000..24f3da6 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/creature_editor.otui @@ -0,0 +1,164 @@ +TargetBotCreatureEditorScrollBar < Panel + height: 28 + margin-top: 3 + + Label + id: text + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-align: center + + HorizontalScrollBar + id: scroll + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 3 + minimum: 0 + maximum: 10 + step: 1 + +TargetBotCreatureEditorTextEdit < Panel + height: 40 + margin-top: 7 + + Label + id: text + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-align: center + + TextEdit + id: textEdit + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 0 + maximum: 10 + step: 1 + +TargetBotCreatureEditorItem < Panel + height: 34 + margin-top: 7 + margin-left: 25 + margin-right: 25 + + Label + id: text + anchors.left: parent.left + anchors.verticalCenter: next.verticalCenter + + BotItem + id: item + anchors.top: parent.top + anchors.right: parent.right + + +TargetBotCreatureEditorCheckBox < BotSwitch + height: 20 + margin-top: 7 + +TargetBotCreatureEditorWindow < MainWindow + text: TargetBot creature editor + width: 500 + height: 630 + + $mobile: + height: 300 + + Label + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-align: center + !text: tr('You can use * (any characters) and ? (any character) in target name') + + Label + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + text-align: center + !text: tr('You can also enter multiple targets, separate them by ,') + + TextEdit + id: name + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-left: 90 + margin-top: 5 + + Label + anchors.verticalCenter: prev.verticalCenter + anchors.left: parent.left + text: Target name: + + VerticalScrollBar + id: contentScroll + anchors.top: name.bottom + anchors.right: parent.right + anchors.bottom: help.top + step: 28 + pixels-scroll: true + margin-right: -10 + margin-top: 5 + margin-bottom: 5 + + ScrollablePanel + id: content + anchors.top: name.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: help.top + vertical-scrollbar: contentScroll + margin-bottom: 10 + + Panel + id: left + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.horizontalCenter + margin-top: 5 + margin-left: 10 + margin-right: 10 + layout: + type: verticalBox + fit-children: true + + Panel + id: right + anchors.top: parent.top + anchors.left: parent.horizontalCenter + anchors.right: parent.right + margin-top: 5 + margin-left: 10 + margin-right: 10 + layout: + type: verticalBox + fit-children: true + + Button + id: help + !text: tr('Help & Tutorials') + anchors.bottom: parent.bottom + anchors.left: parent.left + width: 150 + @onClick: g_platform.openUrl("http://bot.otclient.ovh/") + + Button + id: ok + !text: tr('Ok') + anchors.bottom: parent.bottom + anchors.right: next.left + margin-right: 10 + width: 60 + + Button + id: cancel + !text: tr('Cancel') + anchors.bottom: parent.bottom + anchors.right: parent.right + width: 60 diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/creature_priority.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/creature_priority.lua new file mode 100644 index 0000000..dcc2f81 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/creature_priority.lua @@ -0,0 +1,40 @@ +TargetBot.Creature.calculatePriority = function(creature, config, path) + -- config is based on creature_editor + local priority = 0 + + -- extra priority if it's current target + if g_game.getAttackingCreature() == creature then + priority = priority + 1 + end + + -- check if distance is fine, if not then attack only if already attacked + if #path > config.maxDistance then + return priority + end + + -- add config priority + priority = priority + config.priority + + -- extra priority for close distance + local path_length = #path + if path_length == 1 then + priority = priority + 3 + elseif path_length <= 3 then + priority = priority + 1 + end + + -- extra priority for low health + if config.chase and creature:getHealthPercent() < 30 then + priority = priority + 5 + elseif creature:getHealthPercent() < 20 then + priority = priority + 2.5 + elseif creature:getHealthPercent() < 40 then + priority = priority + 1.5 + elseif creature:getHealthPercent() < 60 then + priority = priority + 0.5 + elseif creature:getHealthPercent() < 80 then + priority = priority + 0.2 + end + + return priority +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/looting.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/looting.lua new file mode 100644 index 0000000..451450f --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/looting.lua @@ -0,0 +1,299 @@ +TargetBot.Looting = {} +TargetBot.Looting.list = {} -- list of containers to loot + +local ui +local items = {} +local containers = {} +local itemsById = {} +local containersById = {} +local dontSave = false + +TargetBot.Looting.setup = function() + ui = UI.createWidget("TargetBotLootingPanel") + UI.Container(TargetBot.Looting.onItemsUpdate, true, nil, ui.items) + UI.Container(TargetBot.Looting.onContainersUpdate, true, nil, ui.containers) + ui.everyItem.onClick = function() + ui.everyItem:setOn(not ui.everyItem:isOn()) + TargetBot.save() + end + ui.maxDangerPanel.value.onTextChange = function() + local value = tonumber(ui.maxDangerPanel.value:getText()) + if not value then + ui.maxDangerPanel.value:setText(0) + end + if dontSave then return end + TargetBot.save() + end + ui.minCapacityPanel.value.onTextChange = function() + local value = tonumber(ui.minCapacityPanel.value:getText()) + if not value then + ui.minCapacityPanel.value:setText(0) + end + if dontSave then return end + TargetBot.save() + end +end + +TargetBot.Looting.onItemsUpdate = function() + if dontSave then return end + TargetBot.save() + TargetBot.Looting.updateItemsAndContainers() +end + +TargetBot.Looting.onContainersUpdate = function() + if dontSave then return end + TargetBot.save() + TargetBot.Looting.updateItemsAndContainers() +end + +TargetBot.Looting.update = function(data) + dontSave = true + TargetBot.Looting.list = {} + ui.items:setItems(data['items'] or {}) + ui.containers:setItems(data['containers'] or {}) + ui.everyItem:setOn(data['everyItem']) + ui.maxDangerPanel.value:setText(data['maxDanger'] or 10) + ui.minCapacityPanel.value:setText(data['minCapacity'] or 100) + TargetBot.Looting.updateItemsAndContainers() + dontSave = false +end + +TargetBot.Looting.save = function(data) + data['items'] = ui.items:getItems() + data['containers'] = ui.containers:getItems() + data['maxDanger'] = tonumber(ui.maxDangerPanel.value:getText()) + data['minCapacity'] = tonumber(ui.minCapacityPanel.value:getText()) + data['everyItem'] = ui.everyItem:isOn() +end + +TargetBot.Looting.updateItemsAndContainers = function() + items = ui.items:getItems() + containers = ui.containers:getItems() + itemsById = {} + containersById = {} + for i, item in ipairs(items) do + itemsById[item.id] = 1 + end + for i, container in ipairs(containers) do + containersById[container.id] = 1 + end +end + +local waitTill = 0 +local waitingForContainer = nil +local status = "" +local lastFoodConsumption = 0 + +TargetBot.Looting.getStatus = function() + return status +end + +TargetBot.Looting.process = function(targets, dangerLevel) + if (not items[1] and not ui.everyItem:isOn()) or not containers[1] then + status = "" + return false + end + if dangerLevel > tonumber(ui.maxDangerPanel.value:getText()) then + status = "High danger" + return false + end + if player:getFreeCapacity() < tonumber(ui.minCapacityPanel.value:getText()) then + status = "No cap" + TargetBot.Looting.list = {} + return false + end + local loot = TargetBot.Looting.list[1] + if loot == nil then + status = "" + return false + end + + if waitTill > now then + return true + end + local containers = g_game.getContainers() + local lootContainers = TargetBot.Looting.getLootContainers(containers) + + -- check if there's container for loot and has empty space for it + if not lootContainers[1] then + -- there's no space, don't loot + status = "No space" + return false + end + + status = "Looting" + + for index, container in pairs(containers) do + if container.lootContainer then + TargetBot.Looting.lootContainer(lootContainers, container) + return true + end + end + + local pos = player:getPosition() + local dist = math.max(math.abs(pos.x-loot.pos.x), math.abs(pos.y-loot.pos.y)) + if loot.tries > 30 or loot.pos.z ~= pos.z or dist > 20 then + table.remove(TargetBot.Looting.list, 1) + return true + end + + local tile = g_map.getTile(loot.pos) + if dist >= 3 or not tile then + loot.tries = loot.tries + 1 + TargetBot.walkTo(loot.pos, 20, { ignoreNonPathable = true, precision = 2 }) + return true + end + + local container = tile:getTopUseThing() + if not container or not container:isContainer() then + table.remove(TargetBot.Looting.list, 1) + return true + end + + g_game.open(container) + waitTill = now + 1000 -- give it 1s to open + waitingForContainer = container:getId() + loot.tries = loot.tries + 10 + + return true +end + +TargetBot.Looting.getLootContainers = function(containers) + local lootContainers = {} + local openedContainersById = {} + local toOpen = nil + for index, container in pairs(containers) do + openedContainersById[container:getContainerItem():getId()] = 1 + if containersById[container:getContainerItem():getId()] and not container.lootContainer then + if container:getItemsCount() < container:getCapacity() then + table.insert(lootContainers, container) + else -- it's full, open next container if possible + for slot, item in ipairs(container:getItems()) do + if item:isContainer() and containersById[item:getId()] then + toOpen = {item, container} + break + end + end + end + end + end + if not lootContainers[1] then + if toOpen then + g_game.open(toOpen[1], toOpen[2]) + waitTill = now + 500 -- wait 0.5s + return lootContainers + end + -- check containers one more time, maybe there's any loot container + for index, container in pairs(containers) do + if not containersById[container:getContainerItem():getId()] and not container.lootContainer then + for slot, item in ipairs(container:getItems()) do + if item:isContainer() and containersById[item:getId()] then + g_game.open(item) + waitTill = now + 500 -- wait 0.5s + return lootContainers + end + end + end + end + -- can't find any lootContainer, let's check slots, maybe there's one + for slot = InventorySlotFirst, InventorySlotLast do + local item = getInventoryItem(slot) + if item and item:isContainer() and not openedContainersById[item:getId()] then + -- container which is not opened yet, let's open it + g_game.open(item) + waitTill = now + 500 -- wait 0.5s + return lootContainers + end + end + end + return lootContainers +end + +TargetBot.Looting.lootContainer = function(lootContainers, container) + -- loot items + local nextContainer = nil + for i, item in ipairs(container:getItems()) do + if item:isContainer() and not itemsById[item:getId()] then + nextContainer = item + elseif itemsById[item:getId()] or (ui.everyItem:isOn() and not item:isContainer()) then + item.lootTries = (item.lootTries or 0) + 1 + if item.lootTries < 5 then -- if can't be looted within 0.5s then skip it + return TargetBot.Looting.lootItem(lootContainers, item) + end + elseif storage.foodItems and storage.foodItems[1] and lastFoodConsumption + 5000 < now then + for _, food in ipairs(storage.foodItems) do + if item:getId() == food.id then + g_game.use(item) + lastFoodConsumption = now + return + end + end + end + end + + -- no more items to loot, open next container + if nextContainer then + nextContainer.lootTries = (nextContainer.lootTries or 0) + 1 + if nextContainer.lootTries < 2 then -- max 0.6s to open it + g_game.open(nextContainer, container) + waitTill = now + 300 -- give it 0.3s to open + waitingForContainer = nextContainer:getId() + return + end + end + + -- looting finished, remove container from list + container.lootContainer = false + g_game.close(container) + table.remove(TargetBot.Looting.list, 1) +end + +TargetBot.Looting.lootItem = function(lootContainers, item) + if item:isStackable() then + local count = item:getCount() + for _, container in ipairs(lootContainers) do + for slot, citem in ipairs(container:getItems()) do + if item:getId() == citem:getId() and citem:getCount() < 100 then + g_game.move(item, container:getSlotPosition(slot - 1), count) + waitTill = now + 300 -- give it 0.3s to move item + return + end + end + end + end + + local container = lootContainers[1] + g_game.move(item, container:getSlotPosition(container:getItemsCount()), 1) + waitTill = now + 300 -- give it 0.3s to move item +end + +onContainerOpen(function(container, previousContainer) + if container:getContainerItem():getId() == waitingForContainer then + container.lootContainer = true + waitingForContainer = nil + end +end) + +onCreatureDisappear(function(creature) + if not TargetBot.isOn() then return end + if not creature:isMonster() then return end + local config = TargetBot.Creature.calculateParams(creature, {}) -- return {craeture, config, danger, priority} + if not config.config or config.config.dontLoot then + return + end + local pos = player:getPosition() + local mpos = creature:getPosition() + local name = creature:getName() + if pos.z ~= mpos.z or math.max(math.abs(pos.x-mpos.x), math.abs(pos.y-mpos.y)) > 6 then return end + schedule(20, function() -- check in 20ms if there's container (dead body) on that tile + if not containers[1] then return end + if TargetBot.Looting.list[20] then return end -- too many items to loot + local tile = g_map.getTile(mpos) + if not tile then return end + local container = tile:getTopUseThing() + if not container or not container:isContainer() then return end + if not findPath(player:getPosition(), mpos, 6, {ignoreNonPathable=true, ignoreCreatures=true, ignoreCost=true}) then return end + table.insert(TargetBot.Looting.list, {pos=mpos, creature=name, container=container:getId(), added=now, tries=0}) + container:setMarked('#000088') + end) +end) diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/looting.otui b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/looting.otui new file mode 100644 index 0000000..97cb351 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/looting.otui @@ -0,0 +1,83 @@ +TargetBotLootingPanel < Panel + layout: + type: verticalBox + fit-children: true + + HorizontalSeparator + margin-top: 5 + + Label + margin-top: 5 + text: Items to loot + text-align: center + + BotContainer + id: items + margin-top: 3 + + BotSwitch + id: everyItem + !text: tr("Loot every item") + margin-top: 2 + + Label + margin-top: 5 + text: Containers for loot + text-align: center + + BotContainer + id: containers + margin-top: 3 + height: 45 + + Panel + id: maxDangerPanel + height: 20 + margin-top: 5 + + BotTextEdit + id: value + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + margin-right: 6 + width: 80 + + Label + anchors.left: parent.left + anchors.verticalCenter: prev.verticalCenter + text: Max. danger: + margin-left: 5 + + Panel + id: minCapacityPanel + height: 20 + margin-top: 3 + + BotTextEdit + id: value + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + margin-right: 6 + width: 80 + + Label + anchors.left: parent.left + anchors.verticalCenter: prev.verticalCenter + text: Min. capacity: + margin-left: 5 + + Label + margin-top: 3 + margin-left: 20 + margin-right: 20 + !text: tr("Drag item or click on any of empty slot") + text-align: center + text-wrap: true + text-auto-resize: true + + BotButton + margin-top: 3 + text: Help & Tutorials + @onClick: g_platform.openUrl("http://bot.otclient.ovh/") diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/target.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/target.lua new file mode 100644 index 0000000..adcd20e --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/target.lua @@ -0,0 +1,285 @@ +local targetbotMacro = nil +local config = nil +local lastAction = 0 +local cavebotAllowance = 0 +local lureEnabled = true + +-- ui +local configWidget = UI.Config() +local ui = UI.createWidget("TargetBotPanel") + +ui.list = ui.listPanel.list -- shortcut +TargetBot.targetList = ui.list +TargetBot.Looting.setup() + +ui.status.left:setText("Status:") +ui.status.right:setText("Off") +ui.target.left:setText("Target:") +ui.target.right:setText("-") +ui.config.left:setText("Config:") +ui.config.right:setText("-") +ui.danger.left:setText("Danger:") +ui.danger.right:setText("0") + +ui.editor.debug.onClick = function() + local on = ui.editor.debug:isOn() + ui.editor.debug:setOn(not on) + if on then + for _, spec in ipairs(getSpectators()) do + spec:clearText() + end + end +end + +-- main loop, controlled by config +targetbotMacro = macro(100, function() + local pos = player:getPosition() + local creatures = g_map.getSpectatorsInRange(pos, false, 6, 6) -- 12x12 area + if #creatures > 10 then -- if there are too many monsters around, limit area + creatures = g_map.getSpectatorsInRange(pos, false, 3, 3) -- 6x6 area + end + local highestPriority = 0 + local dangerLevel = 0 + local targets = 0 + local highestPriorityParams = nil + for i, creature in ipairs(creatures) do + local path = findPath(player:getPosition(), creature:getPosition(), 7, {ignoreLastCreature=true, ignoreNonPathable=true, ignoreCost=true}) + if creature:isMonster() and path then + local params = TargetBot.Creature.calculateParams(creature, path) -- return {craeture, config, danger, priority} + dangerLevel = dangerLevel + params.danger + if params.priority > 0 then + targets = targets + 1 + if params.priority > highestPriority then + highestPriority = params.priority + highestPriorityParams = params + end + if ui.editor.debug:isOn() then + creature:setText(params.config.name .. "\n" .. params.priority) + end + end + end + end + + -- reset walking + TargetBot.walkTo(nil) + + -- looting + local looting = TargetBot.Looting.process(targets, dangerLevel) + local lootingStatus = TargetBot.Looting.getStatus() + + ui.danger.right:setText(dangerLevel) + if highestPriorityParams and not isInPz() then + ui.target.right:setText(highestPriorityParams.creature:getName()) + ui.config.right:setText(highestPriorityParams.config.name) + TargetBot.Creature.attack(highestPriorityParams, targets, looting) + if lootingStatus:len() > 0 then + TargetBot.setStatus("Attack & " .. lootingStatus) + elseif cavebotAllowance > now then + TargetBot.setStatus("Luring using CaveBot") + else + TargetBot.setStatus("Attacking") + if not lureEnabled then + TargetBot.setStatus("Attacking (luring off)") + end + end + TargetBot.walk() + lastAction = now + return + end + + ui.target.right:setText("-") + ui.config.right:setText("-") + if looting then + TargetBot.walk() + lastAction = now + end + if lootingStatus:len() > 0 then + TargetBot.setStatus(lootingStatus) + else + TargetBot.setStatus("Waiting") + end +end) + +-- config, its callback is called immediately, data can be nil +config = Config.setup("targetbot_configs", configWidget, "json", function(name, enabled, data) + if not data then + ui.status.right:setText("Off") + return targetbotMacro.setOff() + end + TargetBot.Creature.resetConfigs() + for _, value in ipairs(data["targeting"] or {}) do + TargetBot.Creature.addConfig(value) + end + TargetBot.Looting.update(data["looting"] or {}) + + -- add configs + if enabled then + ui.status.right:setText("On") + else + ui.status.right:setText("Off") + end + + targetbotMacro.setOn(enabled) + targetbotMacro.delay = nil + lureEnabled = true +end) + +-- setup ui +ui.editor.buttons.add.onClick = function() + TargetBot.Creature.edit(nil, function(newConfig) + TargetBot.Creature.addConfig(newConfig, true) + TargetBot.save() + end) +end + +ui.editor.buttons.edit.onClick = function() + local entry = ui.list:getFocusedChild() + if not entry then return end + TargetBot.Creature.edit(entry.value, function(newConfig) + entry:setText(newConfig.name) + entry.value = newConfig + TargetBot.Creature.resetConfigsCache() + TargetBot.save() + end) +end + +ui.editor.buttons.remove.onClick = function() + local entry = ui.list:getFocusedChild() + if not entry then return end + entry:destroy() + TargetBot.Creature.resetConfigsCache() + TargetBot.save() +end + +-- public function, you can use them in your scripts +TargetBot.isActive = function() -- return true if attacking or looting takes place + return lastAction + 300 > now +end + +TargetBot.isCaveBotActionAllowed = function() + return cavebotAllowance > now +end + +TargetBot.setStatus = function(text) + return ui.status.right:setText(text) +end + +TargetBot.isOn = function() + return config.isOn() +end + +TargetBot.isOff = function() + return config.isOff() +end + +TargetBot.setOn = function(val) + if val == false then + return TargetBot.setOff(true) + end + config.setOn() +end + +TargetBot.setOff = function(val) + if val == false then + return TargetBot.setOn(true) + end + config.setOff() +end + +TargetBot.delay = function(value) + targetbotMacro.delay = now + value +end + +TargetBot.save = function() + local data = {targeting={}, looting={}} + for _, entry in ipairs(ui.list:getChildren()) do + table.insert(data.targeting, entry.value) + end + TargetBot.Looting.save(data.looting) + config.save(data) +end + +TargetBot.allowCaveBot = function(time) + cavebotAllowance = now + time +end + +TargetBot.disableLuring = function() + lureEnabled = false +end + +TargetBot.enableLuring = function() + lureEnabled = true +end + + +-- attacks +local lastSpell = 0 +local lastAttackSpell = 0 + +TargetBot.saySpell = function(text, delay) + if type(text) ~= 'string' or text:len() < 1 then return end + if not delay then delay = 500 end + if g_game.getProtocolVersion() < 1090 then + lastAttackSpell = now -- pause attack spells, healing spells are more important + end + if lastSpell + delay < now then + say(text) + lastSpell = now + return true + end + return false +end + +TargetBot.sayAttackSpell = function(text, delay) + if type(text) ~= 'string' or text:len() < 1 then return end + if not delay then delay = 2000 end + if lastAttackSpell + delay < now then + say(text) + lastAttackSpell = now + return true + end + return false +end + +local lastItemUse = 0 +local lastRuneAttack = 0 + +TargetBot.useItem = function(item, subType, target, delay) + if not delay then delay = 200 end + if lastItemUse + delay < now then + local thing = g_things.getThingType(item) + if not thing or not thing:isFluidContainer() then + subType = g_game.getClientVersion() >= 860 and 0 or 1 + end + if g_game.getClientVersion() < 780 then + local tmpItem = g_game.findPlayerItem(item, subType) + if not tmpItem then return end + g_game.useWith(tmpItem, target, subType) -- using item from bp + else + g_game.useInventoryItemWith(item, target, subType) -- hotkey + end + lastItemUse = now + end +end + +TargetBot.useAttackItem = function(item, subType, target, delay) + if not delay then delay = 2000 end + if lastRuneAttack + delay < now then + local thing = g_things.getThingType(item) + if not thing or not thing:isFluidContainer() then + subType = g_game.getClientVersion() >= 860 and 0 or 1 + end + if g_game.getClientVersion() < 780 then + local tmpItem = g_game.findPlayerItem(item, subType) + if not tmpItem then return end + g_game.useWith(tmpItem, target, subType) -- using item from bp + else + g_game.useInventoryItemWith(item, target, subType) -- hotkey + end + lastRuneAttack = now + end +end + +TargetBot.canLure = function() + return lureEnabled +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/target.otui b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/target.otui new file mode 100644 index 0000000..6e0e4ea --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/target.otui @@ -0,0 +1,115 @@ +TargetBotEntry < Label + background-color: alpha + text-offset: 2 0 + focusable: true + + $focus: + background-color: #00000055 + +TargetBotDualLabel < Panel + height: 18 + margin-left: 3 + margin-right: 4 + + Label + id: left + anchors.top: parent.top + anchors.left: parent.left + text-auto-resize: true + + Label + id: right + anchors.top: parent.top + anchors.right: parent.right + text-auto-resize: true + +TargetBotPanel < Panel + layout: + type: verticalBox + fit-children: true + + HorizontalSeparator + margin-top: 2 + margin-bottom: 5 + + TargetBotDualLabel + id: status + TargetBotDualLabel + id: target + TargetBotDualLabel + id: config + TargetBotDualLabel + id: danger + + Panel + id: listPanel + height: 40 + + TextList + id: list + anchors.fill: parent + vertical-scrollbar: listScrollbar + margin-right: 15 + focusable: false + auto-focus: first + + VerticalScrollBar + id: listScrollbar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + pixels-scroll: true + step: 10 + + BotSwitch + id: configButton + @onClick: | + self:setOn(not self:isOn()) + self:getParent().listPanel:setHeight(self:isOn() and 100 or 40) + self:getParent().editor:setVisible(self:isOn()) + + $on: + text: Hide target editor + + $!on: + text: Show target editor + + Panel + id: editor + visible: false + layout: + type: verticalBox + fit-children: true + + Panel + id: buttons + height: 20 + margin-top: 2 + + Button + id: add + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + text: Add + width: 56 + + Button + id: edit + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + text: Edit + width: 56 + + Button + id: remove + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + text: Remove + width: 56 + + BotSwitch + id: debug + text: Show target priority diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/walking.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/walking.lua new file mode 100644 index 0000000..b256d6a --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot/walking.lua @@ -0,0 +1,28 @@ +local dest +local maxDist +local params + +TargetBot.walkTo = function(_dest, _maxDist, _params) + dest = _dest + maxDist = _maxDist + params = _params +end + +-- called every 100ms if targeting or looting is active +TargetBot.walk = function() + if not dest then return end + if player:isWalking() then return end + local pos = player:getPosition() + if pos.z ~= dest.z then return end + local dist = math.max(math.abs(pos.x-dest.x), math.abs(pos.y-dest.y)) + if params.precision and params.precision >= dist then return end + if params.marginMin and params.marginMax then + if dist >= params.marginMin and dist <= params.marginMax then + return + end + end + local path = getPath(pos, dest, maxDist, params) + if path then + walk(path[1]) + end +end diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot_configs/config_name.json b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot_configs/config_name.json new file mode 100644 index 0000000..e445631 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/targetbot_configs/config_name.json @@ -0,0 +1,53 @@ +{ + "looting": { + "items": [ + + ], + "maxDanger": 10, + "minCapacity": 100, + "containers": [ + { + "count": 1, + "id": 2853 + } + ], + "everyItem": true + }, + "targeting": [ + { + "useSpellAttack": false, + "useRuneAttack": false, + "minMana": 200, + "avoidAttacks": false, + "groupAttackTargets": 2, + "groupAttackSpell": "", + "danger": 1, + "runeAttackDelay": 2000, + "lureCavebot": true, + "dontLoot": false, + "useGroupAttackRune": false, + "groupRuneAttackRadius": 1, + "groupAttackIgnorePlayers": true, + "maxDistance": 10, + "groupAttackIgnoreParty": false, + "lureCount": 5, + "useGroupAttack": false, + "groupRuneAttackTargets": 2, + "attackSpell": "", + "groupAttackRune": 0, + "groupAttackRadius": 1, + "keepDistanceRange": 1, + "groupRuneAttackDelay": 5000, + "priority": 1, + "attackRune": 0, + "groupAttackDelay": 5000, + "minManaGroup": 1500, + "lure": true, + "keepDistance": false, + "attackSpellDelay": 2500, + "chase": true, + "name": "cat, w?lf, snake, troll", + "regex": "^cat$|^w.?lf$|^snake$|^troll$" + } + ] +} \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/cavebot_1.3/tools.lua b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/tools.lua new file mode 100644 index 0000000..7621ab0 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/cavebot_1.3/tools.lua @@ -0,0 +1,147 @@ +-- tools tab +setDefaultTab("Tools") + +-- allows to test/edit bot lua scripts ingame, you can have multiple scripts like this, just change storage.ingame_lua +UI.Button("Ingame macro editor", function(newText) + UI.MultilineEditorWindow(storage.ingame_macros or "", {title="Macro editor", description="You can add your custom macros (or any other lua code) here"}, function(text) + storage.ingame_macros = text + reload() + end) +end) +UI.Button("Ingame hotkey editor", function(newText) + UI.MultilineEditorWindow(storage.ingame_hotkeys or "", {title="Hotkeys editor", description="You can add your custom hotkeys/singlehotkeys here"}, function(text) + storage.ingame_hotkeys = text + reload() + end) +end) + +UI.Separator() + +for _, scripts in ipairs({storage.ingame_macros, storage.ingame_hotkeys}) do + if type(scripts) == "string" and scripts:len() > 3 then + local status, result = pcall(function() + assert(load(scripts, "ingame_editor"))() + end) + if not status then + error("Ingame edior error:\n" .. result) + end + end +end + +UI.Separator() + +UI.Button("Zoom In map [ctrl + =]", function() zoomIn() end) +UI.Button("Zoom Out map [ctrl + -]", function() zoomOut() end) + +UI.Separator() + +local moneyIds = {3031, 3035} -- gold coin, platinium coin +macro(1000, "Exchange money", function() + local containers = g_game.getContainers() + for index, container in pairs(containers) do + if not container.lootContainer then -- ignore monster containers + for i, item in ipairs(container:getItems()) do + if item:getCount() == 100 then + for m, moneyId in ipairs(moneyIds) do + if item:getId() == moneyId then + return g_game.use(item) + end + end + end + end + end + end +end) + +macro(1000, "Stack items", function() + local containers = g_game.getContainers() + local toStack = {} + for index, container in pairs(containers) do + if not container.lootContainer then -- ignore monster containers + for i, item in ipairs(container:getItems()) do + if item:isStackable() and item:getCount() < 100 then + local stackWith = toStack[item:getId()] + if stackWith then + g_game.move(item, stackWith[1], math.min(stackWith[2], item:getCount())) + return + end + toStack[item:getId()] = {container:getSlotPosition(i - 1), 100 - item:getCount()} + end + end + end + end +end) + +macro(10000, "Anti Kick", function() + local dir = player:getDirection() + turn((dir + 1) % 4) + turn(dir) +end) + +UI.Separator() +UI.Label("Drop items:") +if type(storage.dropItems) ~= "table" then + storage.dropItems = {283, 284, 285} +end + +local foodContainer = UI.Container(function(widget, items) + storage.dropItems = items +end, true) +foodContainer:setHeight(35) +foodContainer:setItems(storage.dropItems) + +macro(5000, "drop items", function() + if not storage.dropItems[1] then return end + if TargetBot and TargetBot.isActive() then return end -- pause when attacking + for _, container in pairs(g_game.getContainers()) do + for __, item in ipairs(container:getItems()) do + for i, dropItem in ipairs(storage.dropItems) do + if item:getId() == dropItem.id then + if item:isStackable() then + return g_game.move(item, player:getPosition(), item:getCount()) + else + return g_game.move(item, player:getPosition(), dropItem.count) -- count is also subtype + end + end + end + end + end +end) + +UI.Separator() + +UI.Label("Mana training") +if type(storage.manaTrain) ~= "table" then + storage.manaTrain = {on=false, title="MP%", text="utevo lux", min=80, max=100} +end + +local manatrainmacro = macro(1000, function() + if TargetBot and TargetBot.isActive() then return end -- pause when attacking + local mana = math.min(100, math.floor(100 * (player:getMana() / player:getMaxMana()))) + if storage.manaTrain.max >= mana and mana >= storage.manaTrain.min then + say(storage.manaTrain.text) + end +end) +manatrainmacro.setOn(storage.manaTrain.on) + +UI.DualScrollPanel(storage.manaTrain, function(widget, newParams) + storage.manaTrain = newParams + manatrainmacro.setOn(storage.manaTrain.on) +end) + +UI.Separator() + +macro(60000, "Send message on trade", function() + local trade = getChannelId("advertising") + if not trade then + trade = getChannelId("trade") + end + if trade and storage.autoTradeMessage:len() > 0 then + sayChannel(trade, storage.autoTradeMessage) + end +end) +UI.TextEdit(storage.autoTradeMessage or "I'm using OTClientV8!", function(widget, text) + storage.autoTradeMessage = text +end) + +UI.Separator() diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/_Loader.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/_Loader.lua new file mode 100644 index 0000000..972b0b7 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/_Loader.lua @@ -0,0 +1,65 @@ +-- load all otui files, order doesn't matter +local configName = modules.game_bot.contentsPanel.config:getCurrentOption().text + +local configFiles = g_resources.listDirectoryFiles("/bot/" .. configName .. "/vBot", true, false) +for i, file in ipairs(configFiles) do + local ext = file:split(".") + if ext[#ext]:lower() == "ui" or ext[#ext]:lower() == "otui" then + g_ui.importStyle(file) + end +end + +local function loadScript(name) + return dofile("/vBot/" .. name .. ".lua") +end + +-- here you can set manually order of scripts +-- libraries should be loaded first +local luaFiles = { + "main", + "items", + "vlib", + "new_cavebot_lib", + "configs", -- do not change this and above + "extras", + "cavebot", + "playerlist", + "BotServer", + "alarms", + "Conditions", + "Equipper", + "pushmax", + "combo", + "HealBot", + "new_healer", + "AttackBot", -- last of major modules + "ingame_editor", + "Dropper", + "Containers", + "quiver_manager", + "quiver_label", + "tools", + "antiRs", + "depot_withdraw", + "cast_food", + "eat_food", + "equip", + "exeta", + "analyzer", + "spy_level", + "supplies", + "depositer_config", + "npc_talk", + "xeno_menu", + "hold_target", + "cavebot_control_panel" +} + +for i, file in ipairs(luaFiles) do + loadScript(file) +end + +setDefaultTab("Main") +UI.Separator() +UI.Label("Private Scripts:") +UI.Separator() diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/actions.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/actions.lua new file mode 100644 index 0000000..42e4d59 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/actions.lua @@ -0,0 +1,361 @@ +CaveBot.Actions = {} +vBot.lastLabel = "" +local oldTibia = g_game.getClientVersion() < 960 + + +-- antistuck f() +local nextPos = nil +local function modPos(dir) + local y = 0 + local x = 0 + + if dir == 0 then + y = -1 + elseif dir == 1 then + x = 1 + elseif dir == 2 then + y = 1 + elseif dir == 3 then + x = -1 + elseif dir == 4 then + y = -1 + x = 1 + elseif dir == 5 then + y = 1 + x = 1 + elseif dir == 6 then + y = 1 + x = -1 + elseif dir == 7 then + y = -1 + x = -1 + end + + return {x, y} +end + +-- it adds an action widget to list +CaveBot.addAction = function(action, value, focus) + action = action:lower() + local raction = CaveBot.Actions[action] + if not raction then + return warn("Invalid cavebot action: " .. action) + end + if type(value) == 'number' then + value = tostring(value) + end + local widget = UI.createWidget("CaveBotAction", CaveBot.actionList) + widget:setText(action .. ":" .. value:split("\n")[1]) + widget.action = action + widget.value = value + if raction.color then + widget:setColor(raction.color) + end + widget.onDoubleClick = function(cwidget) -- edit on double click + if CaveBot.Editor then + schedule(20, function() -- schedule to have correct focus + CaveBot.Editor.edit(cwidget.action, cwidget.value, function(action, value) + CaveBot.editAction(cwidget, action, value) + CaveBot.save() + end) + end) + end + end + if focus then + widget:focus() + CaveBot.actionList:ensureChildVisible(widget) + end + return widget +end + +-- it updates existing widget, you should call CaveBot.save() later +CaveBot.editAction = function(widget, action, value) + action = action:lower() + local raction = CaveBot.Actions[action] + if not raction then + return warn("Invalid cavebot action: " .. action) + end + + if not widget.action or not widget.value then + return warn("Invalid cavebot action widget, has missing action or value") + end + + widget:setText(action .. ":" .. value:split("\n")[1]) + widget.action = action + widget.value = value + if raction.color then + widget:setColor(raction.color) + end + return widget +end + +--[[ +registerAction: +action - string, color - string, callback = function(value, retries, prev) +value is a string value of action, retries is number which will grow by 1 if return is "retry" +prev is a true when previuos action was executed succesfully, false otherwise +it must return true if executed correctly, false otherwise +it can also return string "retry", then the function will be called again in 20 ms +]]-- +CaveBot.registerAction = function(action, color, callback) + action = action:lower() + if CaveBot.Actions[action] then + return warn("Duplicated acction: " .. action) + end + CaveBot.Actions[action] = { + color=color, + callback=callback + } +end + +CaveBot.registerAction("label", "yellow", function(value, retries, prev) + vBot.lastLabel = value + return true +end) + +CaveBot.registerAction("gotolabel", "#FFFF55", function(value, retries, prev) + return CaveBot.gotoLabel(value) +end) + +CaveBot.registerAction("delay", "#AAAAAA", function(value, retries, prev) + if retries == 0 then + CaveBot.delay(tonumber(value)) + return "retry" + end + return true +end) + +CaveBot.registerAction("follow", "#FF8400", function(value, retries, prev) + local c = getCreatureByName(value) + if not c then + print("CaveBot[follow]: can't find creature to follow") + return false + end + local cpos = c:getPosition() + local pos = pos() + if getDistanceBetween(cpos, pos) < 2 then + g_game.cancelFollow() + return true + else + follow(c) + delay(200) + return "retry" + end +end) + +CaveBot.registerAction("function", "red", function(value, retries, prev) + local prefix = "local retries = " .. retries .. "\nlocal prev = " .. tostring(prev) .. "\nlocal delay = CaveBot.delay\nlocal gotoLabel = CaveBot.gotoLabel\n" + prefix = prefix .. "local macro = function() warn('Macros inside cavebot functions are not allowed') end\n" + for extension, callbacks in pairs(CaveBot.Extensions) do + prefix = prefix .. "local " .. extension .. " = CaveBot.Extensions." .. extension .. "\n" + end + local status, result = pcall(function() + return assert(load(prefix .. value, "cavebot_function"))() + end) + if not status then + warn("warn in cavebot function:\n" .. result) + return false + end + return result +end) + +CaveBot.registerAction("goto", "green", function(value, retries, prev) + local pos = regexMatch(value, "\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+),?\\s*([0-9]?)") + if not pos[1] then + warn("Invalid cavebot goto action value. It should be position (x,y,z), is: " .. value) + return false + end + + if CaveBot.Config.get("mapClick") then + if retries >= 5 then + return false -- tried 5 times, can't get there + end + else + if retries >= 100 then + return false -- tried 100 times, can't get there + end + end + + local precision = tonumber(pos[1][5]) + pos = {x=tonumber(pos[1][2]), y=tonumber(pos[1][3]), z=tonumber(pos[1][4])} + local playerPos = player:getPosition() + if pos.z ~= playerPos.z then + return false -- different floor + end + + local maxDist = storage.extras.gotoMaxDistance or 40 + + if math.abs(pos.x-playerPos.x) + math.abs(pos.y-playerPos.y) > maxDist then + return false -- too far way + end + + local minimapColor = g_map.getMinimapColor(pos) + local stairs = (minimapColor >= 210 and minimapColor <= 213) + + if stairs then + if math.abs(pos.x-playerPos.x) == 0 and math.abs(pos.y-playerPos.y) <= 0 then + return true -- already at position + end + elseif math.abs(pos.x-playerPos.x) == 0 and math.abs(pos.y-playerPos.y) <= (precision or 1) then + return true -- already at position + end + -- check if there's a path to that place, ignore creatures and fields + local path = findPath(playerPos, pos, maxDist, { ignoreNonPathable = true, precision = 1, ignoreCreatures = true, allowUnseen = true, allowOnlyVisibleTiles = false }) + if not path then + return false -- there's no way + end + + -- check if there's a path to destination but consider Creatures (attack only if trapped) + local path2 = findPath(playerPos, pos, maxDist, { ignoreNonPathable = true, precision = 1 }) + if not path2 then + local foundMonster = false + for i, dir in ipairs(path) do + local dirs = modPos(dir) + nextPos = nextPos or playerPos + nextPos.x = nextPos.x + dirs[1] + nextPos.y = nextPos.y + dirs[2] + + local tile = g_map.getTile(nextPos) + if tile then + if tile:hasCreature() then + local creature = tile:getCreatures()[1] + local hppc = creature:getHealthPercent() + if creature:isMonster() and (hppc and hppc > 0) and (oldTibia or creature:getType() < 3) then + -- real blocking creature can not meet those conditions - ie. it could be player, so just in case check if the next creature is reachable + local path = findPath(playerPos, creature:getPosition(), 20, { ignoreNonPathable = true, precision = 1 }) + if path then + foundMonster = true + attack(creature) + g_game.setChaseMode(1) + CaveBot.setOff() + CaveBot.delay(1000) + schedule(1000, function() CaveBot.setOn() end) + end + end + end + end + end + + nextPos = nil -- reset path + if not foundMonster then + foundMonster = false + return false -- no other way + end + end + + -- try to find path, don't ignore creatures, don't ignore fields + if not CaveBot.Config.get("ignoreFields") and CaveBot.walkTo(pos, 40) then + return "retry" + end + + -- try to find path, don't ignore creatures, ignore fields + if CaveBot.walkTo(pos, maxDist, { ignoreNonPathable = true, allowUnseen = true, allowOnlyVisibleTiles = false }) then + return "retry" + end + + if retries >= 3 then + -- try to lower precision, find something close to final position + local precison = retries - 1 + if stairs then + precison = 0 + end + if CaveBot.walkTo(pos, 50, { ignoreNonPathable = true, precision = precison, allowUnseen = true, allowOnlyVisibleTiles = false }) then + return "retry" + end + end + + if not CaveBot.Config.get("mapClick") and retries >= 5 then + return false + end + + if CaveBot.Config.get("skipBlocked") then + return false + end + + -- everything else failed, try to walk ignoring creatures, maybe will work + CaveBot.walkTo(pos, maxDist, { ignoreNonPathable = true, precision = 1, ignoreCreatures = true, allowUnseen = true, allowOnlyVisibleTiles = false }) + return "retry" +end) + +CaveBot.registerAction("use", "#FFB272", function(value, retries, prev) + local pos = regexMatch(value, "\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)") + if not pos[1] then + local itemid = tonumber(value) + if not itemid then + warn("Invalid cavebot use action value. It should be (x,y,z) or item id, is: " .. value) + return false + end + use(itemid) + return true + end + + pos = {x=tonumber(pos[1][2]), y=tonumber(pos[1][3]), z=tonumber(pos[1][4])} + local playerPos = player:getPosition() + if pos.z ~= playerPos.z then + return false -- different floor + end + + if math.max(math.abs(pos.x-playerPos.x), math.abs(pos.y-playerPos.y)) > 7 then + return false -- too far way + end + + local tile = g_map.getTile(pos) + if not tile then + return false + end + + local topThing = tile:getTopUseThing() + if not topThing then + return false + end + + use(topThing) + CaveBot.delay(CaveBot.Config.get("useDelay") + CaveBot.Config.get("ping")) + return true +end) + +CaveBot.registerAction("usewith", "#EEB292", function(value, retries, prev) + local pos = regexMatch(value, "\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)") + if not pos[1] then + if not itemid then + warn("Invalid cavebot usewith action value. It should be (itemid,x,y,z) or item id, is: " .. value) + return false + end + use(itemid) + return true + end + + local itemid = tonumber(pos[1][2]) + pos = {x=tonumber(pos[1][3]), y=tonumber(pos[1][4]), z=tonumber(pos[1][5])} + local playerPos = player:getPosition() + if pos.z ~= playerPos.z then + return false -- different floor + end + + if math.max(math.abs(pos.x-playerPos.x), math.abs(pos.y-playerPos.y)) > 7 then + return false -- too far way + end + + local tile = g_map.getTile(pos) + if not tile then + return false + end + + local topThing = tile:getTopUseThing() + if not topThing then + return false + end + + usewith(itemid, topThing) + CaveBot.delay(CaveBot.Config.get("useDelay") + CaveBot.Config.get("ping")) + return true +end) + +CaveBot.registerAction("say", "#FF55FF", function(value, retries, prev) + say(value) + return true +end) +CaveBot.registerAction("npcsay", "#FF55FF", function(value, retries, prev) + NPC.say(value) + return true +end) diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/bank.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/bank.lua new file mode 100644 index 0000000..6bdee75 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/bank.lua @@ -0,0 +1,92 @@ +CaveBot.Extensions.Bank = {} + +local balance = 0 + +CaveBot.Extensions.Bank.setup = function() + CaveBot.registerAction("bank", "#db5a5a", function(value, retries) + local data = string.split(value, ",") + local waitVal = 300 + local amount = 0 + local actionType + local npcName + local transferName + local balanceLeft + if #data ~= 3 and #data ~= 2 and #data ~= 4 then + warn("CaveBot[Bank]: incorrect value!") + return false + else + actionType = data[1]:trim():lower() + npcName = data[2]:trim() + if #data == 3 then + amount = tonumber(data[3]:trim()) + end + if #data == 4 then + transferName = data[3]:trim() + balanceLeft = tonumber(data[4]:trim()) + end + end + + if actionType ~= "withdraw" and actionType ~= "deposit" and actionType ~= "transfer" then + warn("CaveBot[Bank]: incorrect action type! should be withdraw/deposit/transfer, is: " .. actionType) + return false + elseif actionType == "withdraw" then + local value = tonumber(amount) + if not value then + warn("CaveBot[Bank]: incorrect amount value! should be number, is: " .. amount) + return false + end + end + + if retries > 5 then + print("CaveBot[Bank]: too many tries, skipping") + return false + end + + local npc = getCreatureByName(npcName) + if not npc then + print("CaveBot[Bank]: NPC not found, skipping") + return false + end + + if not CaveBot.ReachNPC(npcName) then + return "retry" + end + + if actionType == "deposit" then + CaveBot.Conversation("hi", "deposit all", "yes") + CaveBot.delay(storage.extras.talkDelay*3) + return true + elseif actionType == "withdraw" then + CaveBot.Conversation("hi", "withdraw", value, "yes") + CaveBot.delay(storage.extras.talkDelay*4) + return true + else + -- first check balance + CaveBot.Conversation("hi", "balance") + schedule(5000, function() + local amountToTransfer = balance - balanceLeft + if amountToTransfer <= 0 then + warn("CaveBot[Bank] Not enough gold to transfer! proceeding") + return false + end + CaveBot.Conversation("hi", "transfer", amountToTransfer, transferName, "yes") + warn("CaveBot[Bank] transferred "..amountToTransfer.." gold to: "..transferName) + end) + CaveBot.delay(storage.extras.talkDelay*11) + return true + end + end) + + CaveBot.Editor.registerAction("bank", "bank", { + value="action, NPC name", + title="Banker", + description="action type(withdraw/deposit/transfer), NPC name, (if withdraw: amount|if transfer: name, balance left)", + }) +end + + +onTalk(function(name, level, mode, text, channelId, pos) + if mode == 51 and text:find("Your account balance is") then + balance = getFirstNumberInText(text) + end +end) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/buy_supplies.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/buy_supplies.lua new file mode 100644 index 0000000..ccac24f --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/buy_supplies.lua @@ -0,0 +1,88 @@ +CaveBot.Extensions.BuySupplies = {} + +CaveBot.Extensions.BuySupplies.setup = function() + CaveBot.registerAction("BuySupplies", "#C300FF", function(value, retries) + local supplies = SuppliesConfig.supplies + supplies = supplies[supplies.currentProfile] + local item1Count = itemAmount(supplies.item1) + local item2Count = itemAmount(supplies.item2) + local item3Count = itemAmount(supplies.item3) + local item4Count = itemAmount(supplies.item4) + local item5Count = itemAmount(supplies.item5) + local item6Count = itemAmount(supplies.item6) + local item7Count = itemAmount(supplies.item7) + local possibleItems = {} + + local val = string.split(value, ",") + local waitVal + if #val == 0 or #val > 2 then + warn("CaveBot[BuySupplies]: incorrect BuySupplies value") + return false + elseif #val == 2 then + waitVal = tonumber(val[2]:trim()) + end + + local npcName = val[1]:trim() + local npc = getCreatureByName(npcName) + if not npc then + print("CaveBot[BuySupplies]: NPC not found") + return false + end + + if not waitVal and #val == 2 then + warn("CaveBot[BuySupplies]: incorrect delay values!") + elseif waitVal and #val == 2 then + delay(waitVal) + end + + if retries > 50 then + print("CaveBot[BuySupplies]: Too many tries, can't buy") + return false + end + + if not CaveBot.ReachNPC(npcName) then + return "retry" + end + + local itemList = { + item1 = {ID = supplies.item1, maxAmount = supplies.item1Max, currentAmount = item1Count}, + item2 = {ID = supplies.item2, maxAmount = supplies.item2Max, currentAmount = item2Count}, + item3 = {ID = supplies.item3, maxAmount = supplies.item3Max, currentAmount = item3Count}, + item4 = {ID = supplies.item4, maxAmount = supplies.item4Max, currentAmount = item4Count}, + item5 = {ID = supplies.item5, maxAmount = supplies.item5Max, currentAmount = item5Count}, + item6 = {ID = supplies.item6, maxAmount = supplies.item6Max, currentAmount = item6Count}, + item7 = {ID = supplies.item7, maxAmount = supplies.item7Max, currentAmount = item7Count} + } + + if not NPC.isTrading() then + CaveBot.OpenNpcTrade() + CaveBot.delay(storage.extras.talkDelay*2) + return "retry" + end + + -- get items from npc + local npcItems = NPC.getBuyItems() + for i,v in pairs(npcItems) do + table.insert(possibleItems, v.id) + end + + for i, item in pairs(itemList) do + if item["ID"] and item["ID"] > 100 and table.find(possibleItems, item["ID"]) then + local amountToBuy = item["maxAmount"] - item["currentAmount"] + if amountToBuy > 0 then + NPC.buy(item["ID"], math.min(100, amountToBuy)) + print("CaveBot[BuySupplies]: bought " .. math.min(100, amountToBuy) .. "x " .. item["ID"]) + return "retry" + end + end + end + print("CaveBot[BuySupplies]: bought everything, proceeding") + return true + end) + + CaveBot.Editor.registerAction("buysupplies", "buy supplies", { + value="NPC name", + title="Buy Supplies", + description="NPC Name, delay(in ms, optional)", + }) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/cavebot.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/cavebot.lua new file mode 100644 index 0000000..fde0239 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/cavebot.lua @@ -0,0 +1,435 @@ +local cavebotMacro = nil +local config = nil + +-- ui +local configWidget = UI.Config() +local ui = UI.createWidget("CaveBotPanel") + +ui.list = ui.listPanel.list -- shortcut +CaveBot.actionList = ui.list + +if CaveBot.Editor then + CaveBot.Editor.setup() +end +if CaveBot.Config then + CaveBot.Config.setup() +end +for extension, callbacks in pairs(CaveBot.Extensions) do + if callbacks.setup then + callbacks.setup() + end +end + +-- main loop, controlled by config +local actionRetries = 0 +local prevActionResult = true +cavebotMacro = macro(20, function() + if TargetBot and TargetBot.isActive() and not TargetBot.isCaveBotActionAllowed() then + CaveBot.resetWalking() + return -- target bot or looting is working, wait + end + + if CaveBot.doWalking() then + return -- executing walking3 + end + + local actions = ui.list:getChildCount() + if actions == 0 then return end + local currentAction = ui.list:getFocusedChild() + if not currentAction then + currentAction = ui.list:getFirstChild() + end + local action = CaveBot.Actions[currentAction.action] + local value = currentAction.value + local retry = false + if action then + local status, result = pcall(function() + CaveBot.resetWalking() + return action.callback(value, actionRetries, prevActionResult) + end) + if status then + if result == "retry" then + actionRetries = actionRetries + 1 + retry = true + elseif type(result) == 'boolean' then + actionRetries = 0 + prevActionResult = result + else + warn("Invalid return from cavebot action (" .. currentAction.action .. "), should be \"retry\", false or true, is: " .. tostring(result)) + end + else + warn("warn while executing cavebot action (" .. currentAction.action .. "):\n" .. result) + end + else + warn("Invalid cavebot action: " .. currentAction.action) + end + + if retry then + return + end + + if currentAction ~= ui.list:getFocusedChild() then + -- focused child can change durring action, get it again and reset state + currentAction = ui.list:getFocusedChild() or ui.list:getFirstChild() + actionRetries = 0 + prevActionResult = true + end + local nextAction = ui.list:getChildIndex(currentAction) + 1 + if nextAction > actions then + nextAction = 1 + end + ui.list:focusChild(ui.list:getChildByIndex(nextAction)) +end) + +-- config, its callback is called immediately, data can be nil +local lastConfig = "" +config = Config.setup("cavebot_configs", configWidget, "cfg", function(name, enabled, data) + if enabled and CaveBot.Recorder.isOn() then + CaveBot.Recorder.disable() + CaveBot.setOff() + return + end + + local currentActionIndex = ui.list:getChildIndex(ui.list:getFocusedChild()) + ui.list:destroyChildren() + if not data then return cavebotMacro.setOff() end + + local cavebotConfig = nil + for k,v in ipairs(data) do + if type(v) == "table" and #v == 2 then + if v[1] == "config" then + local status, result = pcall(function() + return json.decode(v[2]) + end) + if not status then + warn("warn while parsing CaveBot extensions from config:\n" .. result) + else + cavebotConfig = result + end + elseif v[1] == "extensions" then + local status, result = pcall(function() + return json.decode(v[2]) + end) + if not status then + warn("warn while parsing CaveBot extensions from config:\n" .. result) + else + for extension, callbacks in pairs(CaveBot.Extensions) do + if callbacks.onConfigChange then + callbacks.onConfigChange(name, enabled, result[extension]) + end + end + end + else + CaveBot.addAction(v[1], v[2]) + end + end + end + + CaveBot.Config.onConfigChange(name, enabled, cavebotConfig) + + actionRetries = 0 + CaveBot.resetWalking() + prevActionResult = true + cavebotMacro.setOn(enabled) + cavebotMacro.delay = nil + if lastConfig == name then + -- restore focused child on the action list + ui.list:focusChild(ui.list:getChildByIndex(currentActionIndex)) + end + lastConfig = name +end) + +-- ui callbacks +ui.showEditor.onClick = function() + if not CaveBot.Editor then return end + if ui.showEditor:isOn() then + CaveBot.Editor.hide() + ui.showEditor:setOn(false) + else + CaveBot.Editor.show() + ui.showEditor:setOn(true) + end +end + +ui.showConfig.onClick = function() + if not CaveBot.Config then return end + if ui.showConfig:isOn() then + CaveBot.Config.hide() + ui.showConfig:setOn(false) + else + CaveBot.Config.show() + ui.showConfig:setOn(true) + end +end + +-- public function, you can use them in your scripts +CaveBot.isOn = function() + return config.isOn() +end + +CaveBot.isOff = function() + return config.isOff() +end + +CaveBot.setOn = function(val) + if val == false then + return CaveBot.setOff(true) + end + config.setOn() +end + +CaveBot.setOff = function(val) + if val == false then + return CaveBot.setOn(true) + end + config.setOff() +end + +CaveBot.getCurrentProfile = function() + return storage._configs.cavebot_configs.selected +end + +CaveBot.lastReachedLabel = function() + return vBot.lastLabel +end + +CaveBot.gotoNextWaypointInRange = function() + local currentAction = ui.list:getFocusedChild() + local index = ui.list:getChildIndex(currentAction) + local actions = ui.list:getChildren() + + -- start searching from current index + for i, child in ipairs(actions) do + if i > index then + local text = child:getText() + if string.starts(text, "goto:") then + local re = regexMatch(text, [[(?:goto:)([^,]+),([^,]+),([^,]+)]]) + local pos = {x = tonumber(re[1][2]), y = tonumber(re[1][3]), z = tonumber(re[1][4])} + + if posz() == pos.z then + if distanceFromPlayer(pos) <= storage.extras.gotoMaxDistance/2 then + return ui.list:focusChild(child) + end + end + end + end + end + + -- if not found then damn go from start + for i, child in ipairs(actions) do + if i <= index then + local text = child:getText() + if string.starts(text, "goto:") then + local re = regexMatch(text, [[(?:goto:)([^,]+),([^,]+),([^,]+)]]) + local pos = {x = tonumber(re[1][2]), y = tonumber(re[1][3]), z = tonumber(re[1][4])} + + if posz() == pos.z then + if distanceFromPlayer(pos) <= storage.extras.gotoMaxDistance/2 then + return ui.list:focusChild(child) + end + end + end + end + end + + -- not found + return false +end + +local function reverseTable(t, max) + local reversedTable = {} + local itemCount = max or #t + for i, v in ipairs(t) do + reversedTable[itemCount + 1 - i] = v + end + return reversedTable +end + +function rpairs(t) + test() + return function(t, i) + i = i - 1 + if i ~= 0 then + return i, t[i] + end + end, t, #t + 1 +end + +CaveBot.gotoFirstPreviousReachableWaypoint = function() + local currentAction = ui.list:getFocusedChild() + local currentIndex = ui.list:getChildIndex(currentAction) + local index = ui.list:getChildIndex(currentAction) + + -- check up to 100 childs + for i=0,100 do + index = index - i + if index <= 0 or index > currentIndex or math.abs(index-currentIndex) > 100 then + break + end + + local child = ui.list:getChildByIndex(index) + + if child then + local text = child:getText() + if string.starts(text, "goto:") then + local re = regexMatch(text, [[(?:goto:)([^,]+),([^,]+),([^,]+)]]) + local pos = {x = tonumber(re[1][2]), y = tonumber(re[1][3]), z = tonumber(re[1][4])} + + if posz() == pos.z then + if distanceFromPlayer(pos) <= storage.extras.gotoMaxDistance/2 then + print("found pos, going back "..currentIndex-index.. " waypoints.") + return ui.list:focusChild(child) + end + end + end + end + end + + -- not found + print("previous pos not found, proceeding") + return false +end + +CaveBot.getFirstWaypointBeforeLabel = function(label) + label = "label:"..label + label = label:lower() + local actions = ui.list:getChildren() + local index + + -- find index of label + for i, child in pairs(actions) do + local name = child:getText():lower() + if name == label then + index = i + break + end + end + + -- if there's no index then label was not found + if not index then return false end + + for i=1,#actions do + if index - 1 < 1 then + -- did not found any waypoint in range before label + return false + end + + local child = ui.list:getChildByIndex(index-i) + if child then + local text = child:getText() + if string.starts(text, "goto:") then + local re = regexMatch(text, [[(?:goto:)([^,]+),([^,]+),([^,]+)]]) + local pos = {x = tonumber(re[1][2]), y = tonumber(re[1][3]), z = tonumber(re[1][4])} + + if posz() == pos.z then + if distanceFromPlayer(pos) <= storage.extras.gotoMaxDistance/2 then + return ui.list:focusChild(child) + end + end + end + end + end +end + +CaveBot.getPreviousLabel = function() + local actions = ui.list:getChildren() + -- check if config is empty + if #actions == 0 then return false end + + local currentAction = ui.list:getFocusedChild() + --check we made any progress in waypoints, if no focused or first then no point checking + if not currentAction or currentAction == ui.list:getFirstChild() then return false end + + local index = ui.list:getChildIndex(currentAction) + + -- if not index then something went wrong and there's no selected child + if not index then return false end + + for i=1,#actions do + if index - i < 1 then + -- did not found any waypoint in range before label + return false + end + + local child = ui.list:getChildByIndex(index-i) + if child then + if child.action == "label" then + return child.value + end + end + end +end + +CaveBot.getNextLabel = function() + local actions = ui.list:getChildren() + -- check if config is empty + if #actions == 0 then return false end + + local currentAction = ui.list:getFocusedChild() or ui.list:getFirstChild() + local index = ui.list:getChildIndex(currentAction) + + -- if not index then something went wrong + if not index then return false end + + for i=1,#actions do + if index + i > #actions then + -- did not found any waypoint in range before label + return false + end + + local child = ui.list:getChildByIndex(index+i) + if child then + if child.action == "label" then + return child.value + end + end + end +end + +local botConfigName = modules.game_bot.contentsPanel.config:getCurrentOption().text +CaveBot.setCurrentProfile = function(name) + if not g_resources.fileExists("/bot/"..botConfigName.."/cavebot_configs/"..name..".cfg") then + return warn("there is no cavebot profile with that name!") + end + CaveBot.setOff() + storage._configs.cavebot_configs.selected = name + CaveBot.setOn() +end + +CaveBot.delay = function(value) + cavebotMacro.delay = math.max(cavebotMacro.delay or 0, now + value) +end + +CaveBot.gotoLabel = function(label) + label = label:lower() + for index, child in ipairs(ui.list:getChildren()) do + if child.action == "label" and child.value:lower() == label then + ui.list:focusChild(child) + return true + end + end + return false +end + +CaveBot.save = function() + local data = {} + for index, child in ipairs(ui.list:getChildren()) do + table.insert(data, {child.action, child.value}) + end + + if CaveBot.Config then + table.insert(data, {"config", json.encode(CaveBot.Config.save())}) + end + + local extension_data = {} + for extension, callbacks in pairs(CaveBot.Extensions) do + if callbacks.onSave then + local ext_data = callbacks.onSave() + if type(ext_data) == "table" then + extension_data[extension] = ext_data + end + end + end + table.insert(data, {"extensions", json.encode(extension_data, 2)}) + config.save(data) +end diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/cavebot.otui b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/cavebot.otui new file mode 100644 index 0000000..b92ed05 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/cavebot.otui @@ -0,0 +1,58 @@ +CaveBotAction < Label + background-color: alpha + text-offset: 2 0 + focusable: true + + $focus: + background-color: #00000055 + + +CaveBotPanel < Panel + layout: + type: verticalBox + fit-children: true + + HorizontalSeparator + margin-top: 2 + margin-bottom: 5 + + Panel + id: listPanel + height: 100 + margin-top: 2 + + TextList + id: list + anchors.fill: parent + vertical-scrollbar: listScrollbar + margin-right: 15 + focusable: false + auto-focus: first + + VerticalScrollBar + id: listScrollbar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + pixels-scroll: true + step: 10 + + BotSwitch + id: showEditor + margin-top: 2 + + $on: + text: Hide waypoints editor + + $!on: + text: Show waypoints editor + + BotSwitch + id: showConfig + margin-top: 2 + + $on: + text: Hide config + + $!on: + text: Show config \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/clear_tile.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/clear_tile.lua new file mode 100644 index 0000000..53b6d68 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/clear_tile.lua @@ -0,0 +1,128 @@ +CaveBot.Extensions.ClearTile = {} + +CaveBot.Extensions.ClearTile.setup = function() + CaveBot.registerAction("ClearTile", "#00FFFF", function(value, retries) + local data = string.split(value, ",") + local pos = {x=tonumber(data[1]), y=tonumber(data[2]), z=tonumber(data[3])} + local doors = false + local stand = false + local pPos = player:getPosition() + + + for i, value in ipairs(data) do + value = value:lower():trim() + if value == "stand" then + stand = true + elseif value == "doors" then + doors = true + end + end + + + if not #pos == 3 then + warn("CaveBot[ClearTile]: invalid value. It should be position (x,y,z), is: " .. value) + return false + end + + if retries >= 20 then + print("CaveBot[ClearTile]: too many tries, can't clear it") + return false -- tried 20 times, can't clear it + end + + if getDistanceBetween(player:getPosition(), pos) == 0 then + print("CaveBot[ClearTile]: tile reached, proceeding") + return true + end + local tile = g_map.getTile(pos) + if not tile then + print("CaveBot[ClearTile]: can't find tile or tile is unreachable, skipping") + return false + end + local tPos = tile:getPosition() + + -- no items on tile and walkability means we are done + if tile:isWalkable() and tile:getTopUseThing():isNotMoveable() and not tile:hasCreature() and not doors then + if stand then + if not CaveBot.MatchPosition(tPos, 0) then + CaveBot.GoTo(tPos, 0) + return "retry" + end + print("CaveBot[ClearTile]: tile clear, proceeding") + return true + end + end + + if not CaveBot.MatchPosition(tPos, 3) then + CaveBot.GoTo(tPos, 3) + return "retry" + end + + if retries > 0 then + delay(1100) + end + + -- monster + if tile:hasCreature() then + local c = tile:getCreatures()[1] + if c:isMonster() then + attack(c) + return "retry" + end + end + + -- moveable item + local item = tile:getTopMoveThing() + if item:isItem() then + if item and not item:isNotMoveable() then + print("CaveBot[ClearTile]: moving item... " .. item:getId().. " from tile") + g_game.move(item, pPos, item:getCount()) + return "retry" + end + end + + -- player + + -- push creature + if tile:hasCreature() then + local c = tile:getCreatures()[1] + if c and c:isPlayer() then + + local candidates = {} + for _, tile in ipairs(g_map.getTiles(posz())) do + local tPos = tile:getPosition() + if getDistanceBetween(c:getPosition(), tPos) == 1 and tPos ~= pPos and tile:isWalkable() then + table.insert(candidates, tPos) + end + end + + if #candidates == 0 then + print("CaveBot[ClearTile]: can't find tile to push, cannot clear way, skipping") + return false + else + print("CaveBot[ClearTile]: pushing player... " .. c:getName() .. " out of the way") + local pos = candidates[math.random(1,#candidates)] + local tile = g_map.getTile(pos) + tile:setText("here") + schedule(500, function() tile:setText("") end) + g_game.move(c, pos, 1) + return "retry" + end + end + end + + -- doors + if doors then + use(tile:getTopUseThing()) + return "retry" + end + + return "retry" + end) + + CaveBot.Editor.registerAction("cleartile", "clear tile", { + value=function() return posx() .. "," .. posy() .. "," .. posz() end, + title="position of tile to clear", + description="tile position (x,y,z), optional true if open doors", + multiline=false +}) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/config.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/config.lua new file mode 100644 index 0000000..be6bdc2 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/config.lua @@ -0,0 +1,94 @@ +-- config for bot +CaveBot.Config = {} +CaveBot.Config.values = {} +CaveBot.Config.default_values = {} +CaveBot.Config.value_setters = {} + +CaveBot.Config.setup = function() + CaveBot.Config.ui = UI.createWidget("CaveBotConfigPanel") + local ui = CaveBot.Config.ui + local add = CaveBot.Config.add + + add("ping", "Server ping", 100) + add("walkDelay", "Walk delay", 10) + add("mapClick", "Use map click", false) + add("mapClickDelay", "Map click delay", 100) + add("ignoreFields", "Ignore fields", false) + add("skipBlocked", "Skip blocked path", false) + add("useDelay", "Delay after use", 400) +end + +CaveBot.Config.show = function() + CaveBot.Config.ui:show() +end + +CaveBot.Config.hide = function() + CaveBot.Config.ui:hide() +end + +CaveBot.Config.onConfigChange = function(configName, isEnabled, configData) + for k, v in pairs(CaveBot.Config.default_values) do + CaveBot.Config.value_setters[k](v) + end + if not configData then return end + for k, v in pairs(configData) do + if CaveBot.Config.value_setters[k] then + CaveBot.Config.value_setters[k](v) + end + end +end + +CaveBot.Config.save = function() + return CaveBot.Config.values +end + +CaveBot.Config.add = function(id, title, defaultValue) + if CaveBot.Config.values[id] then + return warn("Duplicated config key: " .. id) + end + + local panel + local setter -- sets value + if type(defaultValue) == "number" then + panel = UI.createWidget("CaveBotConfigNumberValuePanel", CaveBot.Config.ui) + setter = function(value) + CaveBot.Config.values[id] = value + panel.value:setText(value, true) + end + setter(defaultValue) + panel.value.onTextChange = function(widget, newValue) + newValue = tonumber(newValue) + if newValue then + CaveBot.Config.values[id] = newValue + CaveBot.save() + end + end + elseif type(defaultValue) == "boolean" then + panel = UI.createWidget("CaveBotConfigBooleanValuePanel", CaveBot.Config.ui) + setter = function(value) + CaveBot.Config.values[id] = value + panel.value:setOn(value, true) + end + setter(defaultValue) + panel.value.onClick = function(widget) + widget:setOn(not widget:isOn()) + CaveBot.Config.values[id] = widget:isOn() + CaveBot.save() + end + else + return warn("Invalid default value of config for key " .. id .. ", should be number or boolean") + end + + panel.title:setText(tr(title) .. ":") + + CaveBot.Config.value_setters[id] = setter + CaveBot.Config.values[id] = defaultValue + CaveBot.Config.default_values[id] = defaultValue +end + +CaveBot.Config.get = function(id) + if CaveBot.Config.values[id] == nil then + return warn("Invalid CaveBot.Config.get, id: " .. id) + end + return CaveBot.Config.values[id] +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/config.otui b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/config.otui new file mode 100644 index 0000000..21d479d --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/config.otui @@ -0,0 +1,57 @@ +CaveBotConfigPanel < Panel + id: cavebotEditor + visible: false + + layout: + type: verticalBox + fit-children: true + + HorizontalSeparator + margin-top: 5 + + Label + text-align: center + text: CaveBot Config + margin-top: 5 + +CaveBotConfigNumberValuePanel < Panel + height: 20 + margin-top: 5 + + BotTextEdit + id: value + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + margin-right: 5 + width: 50 + + Label + id: title + anchors.left: parent.left + anchors.verticalCenter: prev.verticalCenter + margin-left: 5 + +CaveBotConfigBooleanValuePanel < Panel + height: 20 + margin-top: 5 + + BotSwitch + id: value + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + margin-right: 5 + width: 50 + + $on: + text: On + + $!on: + text: Off + + Label + id: title + anchors.left: parent.left + anchors.verticalCenter: prev.verticalCenter + margin-left: 5 \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/d_withdraw.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/d_withdraw.lua new file mode 100644 index 0000000..888ed1b --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/d_withdraw.lua @@ -0,0 +1,105 @@ +CaveBot.Extensions.DWithdraw = {} + +CaveBot.Extensions.DWithdraw.setup = function() + CaveBot.registerAction("dpwithdraw", "#002FFF", function(value, retries) + local capLimit + local data = string.split(value, ",") + if retries > 600 then + print("CaveBot[DepotWithdraw]: actions limit reached, proceeding") + return false + end + local destContainer + local depotContainer + delay(70) + + -- input validation + if not value or #data ~= 3 and #data ~= 4 then + warn("CaveBot[DepotWithdraw]: incorrect value!") + return false + end + local indexDp = tonumber(data[1]:trim()) + local destName = data[2]:trim():lower() + local destId = tonumber(data[3]:trim()) + if #data == 4 then + capLimit = tonumber(data[4]:trim()) + end + + + -- cap check + if freecap() < (capLimit or 200) then + for i, container in ipairs(getContainers()) do + if container:getName():lower():find("depot") or container:getName():lower():find("locker") then + g_game.close(container) + end + end + print("CaveBot[DepotWithdraw]: cap limit reached, proceeding") + return false + end + + -- containers + for i, container in ipairs(getContainers()) do + local cName = container:getName():lower() + if destName == cName then + destContainer = container + elseif cName:find("depot box") then + depotContainer = container + end + end + + if not destContainer then + print("CaveBot[DepotWithdraw]: container not found!") + return false + end + + if containerIsFull(destContainer) then + for i, item in pairs(destContainer:getItems()) do + if item:getId() == destId then + g_game.open(item, destContainer) + return "retry" + end + end + end + + -- stash validation + if depotContainer and #depotContainer:getItems() == 0 then + print("CaveBot[DepotWithdraw]: all items withdrawn") + g_game.close(depotContainer) + return true + end + + if containerIsFull(destContainer) then + for i, item in pairs(destContainer:getItems()) do + if item:getId() == destId then + g_game.open(foundNextContainer, destContainer) + return "retry" + end + end + print("CaveBot[DepotWithdraw]: loot containers full!") + return false + end + + if not CaveBot.OpenDepotBox(indexDp) then + return "retry" + end + + CaveBot.PingDelay(2) + + for i, container in pairs(g_game.getContainers()) do + if string.find(container:getName():lower(), "depot box") then + for j, item in ipairs(container:getItems()) do + statusMessage("[D_Withdraw] witdhrawing item: "..item:getId()) + g_game.move(item, destContainer:getSlotPosition(destContainer:getItemsCount()), item:getCount()) + return "retry" + end + end + end + + return "retry" + end) + + CaveBot.Editor.registerAction("dpwithdraw", "dpwithdraw", { + value="1, shopping bag, 21411", + title="Loot Withdraw", + description="insert index, destination container name and it's ID", + }) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/depositor.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/depositor.lua new file mode 100644 index 0000000..eb2d038 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/depositor.lua @@ -0,0 +1,138 @@ +CaveBot.Extensions.Depositor = {} + +--local variables +local destination = nil +local lootTable = nil +local reopenedContainers = false + +local function resetCache() + reopenedContainers = false + destination = nil + lootTable = nil + + for i, container in ipairs(getContainers()) do + if container:getName():lower():find("depot") or container:getName():lower():find("locker") then + g_game.close(container) + end + end + + if storage.caveBot.backStop then + storage.caveBot.backStop = false + CaveBot.setOff() + elseif storage.caveBot.backTrainers then + storage.caveBot.backTrainers = false + CaveBot.gotoLabel('toTrainers') + elseif storage.caveBot.backOffline then + storage.caveBot.backOffline = false + CaveBot.gotoLabel('toOfflineTraining') + end +end + +local description = g_game.getClientVersion() > 960 and "No - just deposit \n Yes - also reopen loot containers" or "currently not supported, will be added in near future" + +CaveBot.Extensions.Depositor.setup = function() + CaveBot.registerAction("depositor", "#002FFF", function(value, retries) + -- version check, TODO old tibia + if g_game.getClientVersion() < 960 then + resetCache() + warn("CaveBot[Depositor]: unsupported Tibia version, will be added in near future") + return false + end + + -- loot list check + lootTable = lootTable or CaveBot.GetLootItems() + if #lootTable == 0 then + print("CaveBot[Depositor]: no items in loot list. Wrong TargetBot Config? Proceeding") + resetCache() + return true + end + + delay(70) + + -- backpacks etc + if value:lower() == "yes" then + if not reopenedContainers then + CaveBot.CloseAllLootContainers() + delay(3000) + reopenedContainers = true + return "retry" + end + -- open next backpacks if no more loot + if not CaveBot.HasLootItems() then + local lootContainers = CaveBot.GetLootContainers() + for _, container in ipairs(getContainers()) do + local cId = container:getContainerItem():getId() + if table.find(lootContainers, cId) then + for i, item in ipairs(container:getItems()) do + if item:getId() == cId then + g_game.open(item, container) + delay(100) + return "retry" + end + end + end + end + -- couldn't find next container, so we done + print("CaveBot[Depositor]: all items stashed, no backpack to open next, proceeding") + CaveBot.CloseAllLootContainers() + delay(3000) + resetCache() + return true + end + end + + -- first check items + if retries == 0 then + if not CaveBot.HasLootItems() then -- resource consuming function + print("CaveBot[Depositor]: no items to stash, proceeding") + resetCache() + return true + end + end + + -- next check retries + if retries > 400 then + print("CaveBot[Depositor]: Depositor actions limit reached, proceeding") + resetCache() + return true + end + + -- reaching and opening depot + if not CaveBot.ReachAndOpenDepot() then + return "retry" + end + + -- add delay to prevent bugging + CaveBot.PingDelay(2) + + -- prep time and stashing + destination = destination or getContainerByName("Depot chest") + if not destination then return "retry" end + + for _, container in pairs(getContainers()) do + local name = container:getName():lower() + if not name:find("depot") and not name:find("your inbox") then + for _, item in pairs(container:getItems()) do + local id = item:getId() + if table.find(lootTable, id) then + local index = getStashingIndex(id) or item:isStackable() and 1 or 0 + statusMessage("[Depositer] stashing item: " ..id.. " to depot: "..index+1) + CaveBot.StashItem(item, index, destination) + return "retry" + end + end + end + end + + -- we gucci + resetCache() + return true + end) + + CaveBot.Editor.registerAction("depositor", "depositor", { + value="no", + title="Depositor", + description=description, + validation="(yes|Yes|YES|no|No|NO)" + }) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/doors.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/doors.lua new file mode 100644 index 0000000..f53992b --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/doors.lua @@ -0,0 +1,58 @@ +CaveBot.Extensions.OpenDoors = {} + +CaveBot.Extensions.OpenDoors.setup = function() + CaveBot.registerAction("OpenDoors", "#00FFFF", function(value, retries) + local pos = string.split(value, ",") + local key = nil + if #pos == 4 then + key = tonumber(pos[4]) + end + if not pos[1] then + warn("CaveBot[OpenDoors]: invalid value. It should be position (x,y,z), is: " .. value) + return false + end + + if retries >= 5 then + print("CaveBot[OpenDoors]: too many tries, can't open doors") + return false -- tried 5 times, can't open + end + + pos = {x=tonumber(pos[1]), y=tonumber(pos[2]), z=tonumber(pos[3])} + + local doorTile + if not doorTile then + for i, tile in ipairs(g_map.getTiles(posz())) do + if tile:getPosition().x == pos.x and tile:getPosition().y == pos.y and tile:getPosition().z == pos.z then + doorTile = tile + end + end + end + + if not doorTile then + return false + end + + if not doorTile:isWalkable() then + if not key then + use(doorTile:getTopUseThing()) + delay(200) + return "retry" + else + useWith(key, doorTile:getTopUseThing()) + delay(200) + return "retry" + end + else + print("CaveBot[OpenDoors]: possible to cross, proceeding") + return true + end + end) + + CaveBot.Editor.registerAction("opendoors", "open doors", { + value=function() return posx() .. "," .. posy() .. "," .. posz() end, + title="Door position", + description="doors position (x,y,z) and key id (optional)", + multiline=false, + validation=[[\d{1,5},\d{1,5},\d{1,2}(?:,\d{1,5}$|$)]] +}) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/editor.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/editor.lua new file mode 100644 index 0000000..a75f1be --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/editor.lua @@ -0,0 +1,186 @@ +CaveBot.Editor = {} +CaveBot.Editor.Actions = {} + +-- also works as registerAction(action, params), then text == action +-- params are options for text editor or function to be executed when clicked +-- you have many examples how to use it bellow +CaveBot.Editor.registerAction = function(action, text, params) + if type(text) ~= 'string' then + params = text + text = action + end + + local color = nil + if type(params) ~= 'function' then + local raction = CaveBot.Actions[action] + if not raction then + return warn("CaveBot editor warn: action " .. action .. " doesn't exist") + end + CaveBot.Editor.Actions[action] = params + color = raction.color + end + + local button = UI.createWidget('CaveBotEditorButton', CaveBot.Editor.ui.buttons) + button:setText(text) + if color then + button:setColor(color) + end + button.onClick = function() + if type(params) == 'function' then + params() + return + end + CaveBot.Editor.edit(action, nil, function(action, value) + local focusedAction = CaveBot.actionList:getFocusedChild() + local index = CaveBot.actionList:getChildCount() + if focusedAction then + index = CaveBot.actionList:getChildIndex(focusedAction) + end + local widget = CaveBot.addAction(action, value) + CaveBot.actionList:moveChildToIndex(widget, index + 1) + CaveBot.actionList:focusChild(widget) + CaveBot.save() + end) + end + return button +end + +CaveBot.Editor.setup = function() + CaveBot.Editor.ui = UI.createWidget("CaveBotEditorPanel") + local ui = CaveBot.Editor.ui + local registerAction = CaveBot.Editor.registerAction + + registerAction("move up", function() + local action = CaveBot.actionList:getFocusedChild() + if not action then return end + local index = CaveBot.actionList:getChildIndex(action) + if index < 2 then return end + CaveBot.actionList:moveChildToIndex(action, index - 1) + CaveBot.actionList:ensureChildVisible(action) + CaveBot.save() + end) + registerAction("edit", function() + local action = CaveBot.actionList:getFocusedChild() + if not action or not action.onDoubleClick then return end + action.onDoubleClick(action) + end) + registerAction("move down", function() + local action = CaveBot.actionList:getFocusedChild() + if not action then return end + local index = CaveBot.actionList:getChildIndex(action) + if index >= CaveBot.actionList:getChildCount() then return end + CaveBot.actionList:moveChildToIndex(action, index + 1) + CaveBot.actionList:ensureChildVisible(action) + CaveBot.save() + end) + registerAction("remove", function() + local action = CaveBot.actionList:getFocusedChild() + if not action then return end + action:destroy() + CaveBot.save() + end) + + registerAction("label", { + value="labelName", + title="Label", + description="Add label", + multiline=false + }) + registerAction("delay", { + value="500", + title="Delay", + description="Delay next action (in milliseconds)", + multiline=false, + validation="^\\s*[0-9]{1,10}\\s*$" + }) + registerAction("gotolabel", "go to label", { + value="labelName", + title="Go to label", + description="Go to label", + multiline=false + }) + registerAction("goto", "go to", { + value=function() return posx() .. "," .. posy() .. "," .. posz() end, + title="Go to position", + description="Go to position (x,y,z)", + multiline=false, + validation="^\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+),?\\s*([0-9]?)$" + }) + registerAction("use", { + value=function() return posx() .. "," .. posy() .. "," .. posz() end, + title="Use", + description="Use item from position (x,y,z) or from inventory (itemId)", + multiline=false + }) + registerAction("usewith", "use with", { + value=function() return "itemId," .. posx() .. "," .. posy() .. "," .. posz() end, + title="Use with", + description="Use item at position (itemid,x,y,z)", + multiline=false, + validation="^\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)$" + }) + registerAction("say", { + value="text", + title="Say", + description="Enter text to say", + multiline=false + }) + registerAction("follow", { + value="NPC name", + title="Follow Creature", + description="insert creature name to follow", + multiline=false + }) + registerAction("npcsay", { + value="text", + title="NPC Say", + description="Enter text to NPC say", + multiline=false + }) + registerAction("function", { + title="Edit bot function", + multiline=true, + value=CaveBot.Editor.ExampleFunctions[1][2], + examples=CaveBot.Editor.ExampleFunctions, + width=650 + }) + + ui.autoRecording.onClick = function() + if ui.autoRecording:isOn() then + CaveBot.Recorder.disable() + else + CaveBot.Recorder.enable() + end + end + + -- callbacks + onPlayerPositionChange(function(pos) + ui.pos:setText("Position: " .. pos.x .. ", " .. pos.y .. ", " .. pos.z) + end) + ui.pos:setText("Position: " .. posx() .. ", " .. posy() .. ", " .. posz()) +end + +CaveBot.Editor.show = function() + CaveBot.Editor.ui:show() +end + + +CaveBot.Editor.hide = function() + CaveBot.Editor.ui:hide() +end + +CaveBot.Editor.edit = function(action, value, callback) -- callback = function(action, value) + local params = CaveBot.Editor.Actions[action] + if not params then return end + if not value then + if type(params.value) == 'function' then + value = params.value() + elseif type(params.value) == 'string' then + value = params.value + end + end + + UI.EditorWindow(value, params, function(newText) + callback(action, newText) + end) +end diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/editor.otui b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/editor.otui new file mode 100644 index 0000000..d11288c --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/editor.otui @@ -0,0 +1,44 @@ +CaveBotEditorButton < Button + + +CaveBotEditorPanel < Panel + id: cavebotEditor + visible: false + layout: + type: verticalBox + fit-children: true + + Label + id: pos + text-align: center + text: - + + Panel + id: buttons + margin-top: 2 + layout: + type: grid + cell-size: 86 20 + cell-spacing: 1 + flow: true + fit-children: true + + Label + text: Double click on action from action list to edit it + text-align: center + text-auto-resize: true + text-wrap: true + margin-top: 3 + margin-left: 2 + margin-right: 2 + + BotSwitch + id: autoRecording + text: Auto Recording + margin-top: 3 + + BotButton + margin-top: 3 + margin-bottom: 3 + text: Documentation + @onClick: g_platform.openUrl("http://bot.otclient.ovh/") diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/example_functions.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/example_functions.lua new file mode 100644 index 0000000..99252e8 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/example_functions.lua @@ -0,0 +1,114 @@ +CaveBot.Editor.ExampleFunctions = {} + +local function addExampleFunction(title, text) + return table.insert(CaveBot.Editor.ExampleFunctions, {title, text:trim()}) +end + +addExampleFunction("Click to browse example functions", [[ +-- available functions/variables: +-- prev - result of previous action (true or false) +-- retries - number of retries of current function, goes up by one when you return "retry" +-- delay(number) - delays bot next action, value in milliseconds +-- gotoLabel(string) - goes to specific label, return true if label exists +-- you can easily access bot extensions, Depositer.run() instead of CaveBot.Extensions.Depositer.run() +-- also you can access bot global variables, like CaveBot, TargetBot +-- use storage variable to store date between calls + +-- function should return false, true or "retry" +-- if "retry" is returned, function will be executed again in 20 ms (so better call delay before) + +return true +]]) + +addExampleFunction("Check for PZ and wait until dropped", [[ +if retries > 25 or not isPzLocked() then + return true +else + if isPoisioned() then + say("exana pox") + end + if isPzLocked() then + delay(8000) + end + return "retry" +end +]]) + +addExampleFunction("Check for stamina and imbues", [[ + if stamina() < 900 or player:getSkillLevel(11) == 0 then CaveBot.setOff() return false else return true end +]]) + +addExampleFunction("buy 200 mana potion from npc Eryn", [[ +--buy 200 mana potions +local npc = getCreatureByName("Eryn") +if not npc then + return false +end +if retries > 10 then + return false +end +local pos = player:getPosition() +local npcPos = npc:getPosition() +if math.max(math.abs(pos.x - npcPos.x), math.abs(pos.y - npcPos.y)) > 3 then + autoWalk(npcPos, {precision=3}) + delay(300) + return "retry" +end +if not NPC.isTrading() then + NPC.say("hi") + NPC.say("trade") + delay(200) + return "retry" +end +NPC.buy(268, 100) +schedule(1000, function() + -- buy again in 1s + NPC.buy(268, 100) + NPC.closeTrade() + NPC.say("bye") +end) +delay(1200) +return true +]]) + +addExampleFunction("Say hello 5 times with some delay", [[ +--say hello +if retries > 5 then + return true -- finish +end +say("hello") +delay(100 + retries * 100) +return "retry" +]]) + +addExampleFunction("Disable TargetBot", [[ +TargetBot.setOff() +return true +]]) + +addExampleFunction("Enable TargetBot", [[ +TargetBot.setOn() +return true +]]) + +addExampleFunction("Enable TargetBot luring", [[ +TargetBot.enableLuring() +return true +]]) + +addExampleFunction("Disable TargetBot luring", [[ +TargetBot.disableLuring() +return true +]]) + +addExampleFunction("Logout", [[ +g_game.safeLogout() +delay(1000) +return "retry" +]]) + +addExampleFunction("Close Loot Containers", [[ +CaveBot.CloseAllLootContainers() +delay(3000) +return true +]]) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/extension_template.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/extension_template.lua new file mode 100644 index 0000000..d015f11 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/extension_template.lua @@ -0,0 +1,58 @@ +-- example cavebot extension (remember to add this file to ../cavebot.lua) +CaveBot.Extensions.Example = {} + +local ui + +-- setup is called automaticly when cavebot is ready +CaveBot.Extensions.Example.setup = function() + ui = UI.createWidget('BotTextEdit') + ui:setText("Hello") + ui.onTextChange = function() + CaveBot.save() -- save new config when you change something + end + + -- add custom cavebot action (check out actions.lua) + CaveBot.registerAction("sayhello", "orange", function(value, retries, prev) + local how_many_times = tonumber(value) + if retries >= how_many_times then + return true + end + say("hello " .. (retries + 1)) + delay(250) + return "retry" + end) + + -- add this custom action to editor (check out editor.lua) + CaveBot.Editor.registerAction("sayhello", "say hello", { + value="5", + title="Say hello", + description="Says hello x times", + validation="[0-9]{1,5}" -- regex, optional + }) +end + +-- called when cavebot config changes, configData is a table but it can also be nil +CaveBot.Extensions.Example.onConfigChange = function(configName, isEnabled, configData) + if not configData then return end + if configData["text"] then + ui:setText(configData["text"]) + end +end + +-- called when cavebot is saving config (so when CaveBot.save() is called), should return table or nil +CaveBot.Extensions.Example.onSave = function() + return {text=ui:getText()} +end + +-- bellow add you custom functions to be used in cavebot function action +-- an example: return Example.run(retries, prev) +-- there are 2 useful parameters - retries (number) and prev (true/false), check actions.lua and example_functions.lua to learn more +CaveBot.Extensions.Example.run = function(retries, prev) + -- it will say text 10 times with some delay and then continue + if retries > 10 then + return true + end + say(ui:getText() .. " x" .. retries) + delay(100 + retries * 100) + return "retry" +end diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/imbuing.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/imbuing.lua new file mode 100644 index 0000000..5cd6f7c --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/imbuing.lua @@ -0,0 +1,112 @@ +-- imbuing window should be handled separatly +-- reequiping should be handled separatly (ie. equipment manager) + +CaveBot.Extensions.Imbuing = {} + +local SHRINES = {25060, 25061, 25182, 25183} +local currentIndex = 1 +local shrine = nil +local item = nil +local currentId = 0 +local triedToTakeOff = false +local destination = nil + +local function reset() + EquipManager.setOn() + shrine = nil + currentIndex = 1 + item = nil + currentId = 0 + triedToTakeOff = false + destination = nil +end + +CaveBot.Extensions.Imbuing.setup = function() + CaveBot.registerAction("imbuing", "red", function(value, retries) + local data = string.split(value, ",") + local ids = {} + + if #data == 0 then + warn("CaveBot[Imbuing] no items added, proceeding") + reset() + return false + end + + -- setting of equipment manager so it wont disturb imbuing process + EquipManager.setOff() + + -- convert to number + for i, id in ipairs(data) do + id = tonumber(id) + if not table.find(ids, id) then + table.insert(ids, id) + end + end + + -- all items imbued, can proceed + if currentIndex > #ids then + warn("CaveBot[Imbuing] used shrine on all items, proceeding") + reset() + return true + end + + for _, tile in ipairs(g_map.getTiles(posz())) do + for _, item in ipairs(tile:getItems()) do + local id = item:getId() + if table.find(SHRINES, id) then + shrine = item + break + end + end + end + + -- if not shrine + if not shrine then + warn("CaveBot[Imbuing] shrine not found! proceeding") + reset() + return false + end + + destination = shrine:getPosition() + + currentId = ids[currentIndex] + item = findItem(currentId) + + -- maybe equipped? try to take off + if not item then + -- did try before, still not found so item is unavailable + if triedToTakeOff then + warn("CaveBot[Imbuing] item not found! skipping: "..currentId) + triedToTakeOff = false + currentIndex = currentIndex + 1 + return "retry" + end + triedToTakeOff = true + g_game.equipItemId(currentId) + delay(1000) + return "retry" + end + + -- we are past unequiping so just in case we were forced before, reset var + triedToTakeOff = false + + -- reaching shrine + if not CaveBot.MatchPosition(destination, 1) then + CaveBot.GoTo(destination, 1) + delay(200) + return "retry" + end + + useWith(shrine, item) + currentIndex = currentIndex + 1 + warn("CaveBot[Imbuing] Using shrine on item: "..currentId) + delay(4000) + return "retry" + end) + + CaveBot.Editor.registerAction("imbuing", "imbuing", { + value="item id 1, item id 2", + title="Auto Imbuing", + description="insert below item ids to be imbued, separated by comma", + }) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/inbox_withdraw.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/inbox_withdraw.lua new file mode 100644 index 0000000..d5fc02b --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/inbox_withdraw.lua @@ -0,0 +1,91 @@ +CaveBot.Extensions.InWithdraw = {} + +CaveBot.Extensions.InWithdraw.setup = function() + CaveBot.registerAction("inwithdraw", "#002FFF", function(value, retries) + local data = string.split(value, ",") + local withdrawId + local amount + + -- validation + if #data ~= 2 then + warn("CaveBot[InboxWithdraw]: incorrect withdraw value") + return false + else + withdrawId = tonumber(data[1]) + amount = tonumber(data[2]) + end + + local currentAmount = itemAmount(withdrawId) + + if currentAmount >= amount then + print("CaveBot[InboxWithdraw]: enough items, proceeding") + return true + end + + if retries > 400 then + print("CaveBot[InboxWithdraw]: actions limit reached, proceeding") + return true + end + + -- actions + local inboxContainer = getContainerByName("your inbox") + delay(100) + if not inboxContainer then + if not CaveBot.ReachAndOpenInbox() then + return "retry" + end + end + local inboxAmount = 0 + if not inboxContainer then + return "retry" + end + for i, item in pairs(inboxContainer:getItems()) do + if item:getId() == withdrawId then + inboxAmount = inboxAmount + item:getCount() + end + end + if inboxAmount == 0 then + warn("CaveBot[InboxWithdraw]: not enough items in inbox container, proceeding") + g_game.close(inboxContainer) + return true + end + + local destination + for i, container in pairs(getContainers()) do + if container:getCapacity() > #container:getItems() and not string.find(container:getName():lower(), "quiver") and not string.find(container:getName():lower(), "depot") and not string.find(container:getName():lower(), "loot") and not string.find(container:getName():lower(), "inbox") then + destination = container + end + end + + if not destination then + print("CaveBot[InboxWithdraw]: couldn't find proper destination container, skipping") + g_game.close(inboxContainer) + return false + end + + CaveBot.PingDelay(2) + + for i, container in pairs(getContainers()) do + if string.find(container:getName():lower(), "your inbox") then + for j, item in pairs(container:getItems()) do + if item:getId() == withdrawId then + if item:isStackable() then + g_game.move(item, destination:getSlotPosition(destination:getItemsCount()), math.min(item:getCount(), (amount - currentAmount))) + return "retry" + else + g_game.move(item, destination:getSlotPosition(destination:getItemsCount()), 1) + return "retry" + end + return "retry" + end + end + end + end + end) + + CaveBot.Editor.registerAction("inwithdraw", "in withdraw", { + value="id,amount", + title="Withdraw Items", + description="insert item id and amount", + }) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/lure.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/lure.lua new file mode 100644 index 0000000..0cb5c54 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/lure.lua @@ -0,0 +1,29 @@ +CaveBot.Extensions.Lure = {} + +CaveBot.Extensions.Lure.setup = function() + CaveBot.registerAction("lure", "#FF0090", function(value, retries) + value = value:lower() + if value == "start" then + TargetBot.setOff() + elseif value == "stop" then + TargetBot.setOn() + elseif value == "toggle" then + if TargetBot.isOn() then + TargetBot.setOff() + else + TargetBot.setOn() + end + else + warn("incorrect lure value!") + end + return true + end) + + CaveBot.Editor.registerAction("lure", "lure", { + value="toggle", + title="Lure", + description="TargetBot: start, stop, toggle", + multiline=false, + validation=[[(start|stop|toggle)$]] +}) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/minimap.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/minimap.lua new file mode 100644 index 0000000..5ace5ee --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/minimap.lua @@ -0,0 +1,26 @@ +local minimap = modules.game_minimap.minimapWidget + +minimap.onMouseRelease = function(widget,pos,button) + if not minimap.allowNextRelease then return true end + minimap.allowNextRelease = false + + local mapPos = minimap:getTilePosition(pos) + if not mapPos then return end + + if button == 1 then + local player = g_game.getLocalPlayer() + if minimap.autowalk then + player:autoWalk(mapPos) + end + return true + elseif button == 2 then + local menu = g_ui.createWidget('PopupMenu') + menu:setId("minimapMenu") + menu:setGameMenu(true) + menu:addOption(tr('Create mark'), function() minimap:createFlagWindow(mapPos) end) + menu:addOption(tr('Add CaveBot GoTo'), function() CaveBot.addAction("goto", mapPos.x .. "," .. mapPos.y .. "," .. mapPos.z, true) CaveBot.save() end) + menu:display(pos) + return true + end + return false +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/pos_check.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/pos_check.lua new file mode 100644 index 0000000..361ddb6 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/pos_check.lua @@ -0,0 +1,47 @@ +CaveBot.Extensions.PosCheck = {} + +local posCheckRetries = 0 +CaveBot.Extensions.PosCheck.setup = function() + CaveBot.registerAction("PosCheck", "#00FFFF", function(value, retries) + local tilePos + local data = string.split(value, ",") + if #data ~= 5 then + warn("wrong travel format, should be: label, distance, x, y, z") + return false + end + + local tilePos = player:getPosition() + + tilePos.x = tonumber(data[3]) + tilePos.y = tonumber(data[4]) + tilePos.z = tonumber(data[5]) + + if posCheckRetries > 10 then + posCheckRetries = 0 + print("CaveBot[CheckPos]: waypoints locked, too many tries, unclogging cavebot and proceeding") + return false + elseif (tilePos.z == player:getPosition().z) and (getDistanceBetween(player:getPosition(), tilePos) <= tonumber(data[2])) then + posCheckRetries = 0 + print("CaveBot[CheckPos]: position reached, proceeding") + return true + else + posCheckRetries = posCheckRetries + 1 + if data[1] == "last" then + CaveBot.gotoFirstPreviousReachableWaypoint() + print("CaveBot[CheckPos]: position not-reached, going back to first reachable waypoint.") + return false + else + CaveBot.gotoLabel(data[1]) + print("CaveBot[CheckPos]: position not-reached, going back to label: " .. data[1]) + return false + end + end + end) + + CaveBot.Editor.registerAction("poscheck", "pos check", { + value=function() return "last" .. "," .. "10" .. "," .. posx() .. "," .. posy() .. "," .. posz() end, + title="Location Check", + description="label name, accepted dist from coordinates, x, y, z", + multiline=false, +}) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/recorder.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/recorder.lua new file mode 100644 index 0000000..14248f3 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/recorder.lua @@ -0,0 +1,69 @@ +-- auto recording for cavebot +CaveBot.Recorder = {} + +local isEnabled = nil +local lastPos = nil + +local function setup() + local function addPosition(pos) + CaveBot.addAction("goto", pos.x .. "," .. pos.y .. "," .. pos.z, true) + lastPos = pos + end + local function addStairs(pos) + CaveBot.addAction("goto", pos.x .. "," .. pos.y .. "," .. pos.z .. ",0", true) + lastPos = pos + end + + onPlayerPositionChange(function(newPos, oldPos) + if CaveBot.isOn() or not isEnabled then return end + if not lastPos then + -- first step + addPosition(oldPos) + elseif newPos.z ~= oldPos.z or math.abs(oldPos.x - newPos.x) > 1 or math.abs(oldPos.y - newPos.y) > 1 then + -- stairs/teleport + addStairs(oldPos) + elseif math.max(math.abs(lastPos.x - newPos.x), math.abs(lastPos.y - newPos.y)) > 5 then + -- 5 steps from last pos + addPosition(newPos) + end + end) + + onUse(function(pos, itemId, stackPos, subType) + if CaveBot.isOn() or not isEnabled then return end + if pos.x ~= 0xFFFF then + lastPos = pos + CaveBot.addAction("use", pos.x .. "," .. pos.y .. "," .. pos.z, true) + end + end) + + onUseWith(function(pos, itemId, target, subType) + if CaveBot.isOn() or not isEnabled then return end + if not target:isItem() then return end + local targetPos = target:getPosition() + if targetPos.x == 0xFFFF then return end + lastPos = pos + CaveBot.addAction("usewith", itemId .. "," .. targetPos.x .. "," .. targetPos.y .. "," .. targetPos.z, true) + end) +end + +CaveBot.Recorder.isOn = function() + return isEnabled +end + +CaveBot.Recorder.enable = function() + CaveBot.setOff() + if isEnabled == nil then + setup() + end + CaveBot.Editor.ui.autoRecording:setOn(true) + isEnabled = true + lastPos = nil +end + +CaveBot.Recorder.disable = function() + if isEnabled == true then + isEnabled = false + end + CaveBot.Editor.ui.autoRecording:setOn(false) + CaveBot.save() +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/sell_all.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/sell_all.lua new file mode 100644 index 0000000..579f6cb --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/sell_all.lua @@ -0,0 +1,65 @@ +CaveBot.Extensions.SellAll = {} + +local sellAllCap = 0 +CaveBot.Extensions.SellAll.setup = function() + CaveBot.registerAction("SellAll", "#C300FF", function(value, retries) + local val = string.split(value, ",") + local wait + + -- table formatting + for i, v in ipairs(val) do + v = v:trim() + v = tonumber(v) or v + val[i] = v + end + + if table.find(val, "yes", true) then + wait = true + end + + local npcName = val[1] + local npc = getCreatureByName(npcName) + if not npc then + print("CaveBot[SellAll]: NPC not found! skipping") + return false + end + + if retries > 10 then + print("CaveBot[SellAll]: can't sell, skipping") + return false + end + + if freecap() == sellAllCap then + sellAllCap = 0 + print("CaveBot[SellAll]: Sold everything, proceeding") + return true + end + + delay(800) + if not CaveBot.ReachNPC(npcName) then + return "retry" + end + + if not NPC.isTrading() then + CaveBot.OpenNpcTrade() + delay(storage.extras.talkDelay*2) + else + sellAllCap = freecap() + end + + modules.game_npctrade.sellAll(wait, val) + if wait then + print("CaveBot[SellAll]: Sold All with delay") + else + print("CaveBot[SellAll]: Sold All without delay") + end + + return "retry" + end) + + CaveBot.Editor.registerAction("sellall", "sell all", { + value="NPC", + title="Sell All", + description="NPC Name, 'yes' if sell with delay, exceptions: id separated by comma", + }) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/supply_check.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/supply_check.lua new file mode 100644 index 0000000..172db5b --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/supply_check.lua @@ -0,0 +1,142 @@ +CaveBot.Extensions.SupplyCheck = {} + +local supplyRetries = 0 +local missedChecks = 0 +local time = nil +CaveBot.Extensions.SupplyCheck.setup = function() + CaveBot.registerAction("supplyCheck", "#db5a5a", function(value) + local data = string.split(value, ",") + local round = 0 + local label = data[1]:trim() + local pos = nil + if #data == 4 then + pos = {x=tonumber(data[2]),y=tonumber(data[3]),z=tonumber(data[4])} + end + + if pos then + if missedChecks >= 4 then + missedChecks = 0 + supplyRetries = 0 + print("CaveBot[SupplyCheck]: Missed 5 supply checks, proceeding with waypoints") + return true + end + if getDistanceBetween(player:getPosition(), pos) > 10 then + missedChecks = missedChecks + 1 + print("CaveBot[SupplyCheck]: Missed supply check! ".. 5-missedChecks .. " tries left before skipping.") + return CaveBot.gotoLabel(label) + end + end + + if time then + round = math.ceil((now - time)/1000) .. "s" + else + round = "" + end + time = now + + local supplies = SuppliesConfig.supplies + supplies = supplies[supplies.currentProfile] + local softCount = itemAmount(6529) + itemAmount(3549) + local totalItem1 = itemAmount(supplies.item1) + local totalItem2 = itemAmount(supplies.item2) + local totalItem3 = itemAmount(supplies.item3) + local totalItem4 = itemAmount(supplies.item4) + local totalItem5 = itemAmount(supplies.item5) + local totalItem6 = itemAmount(supplies.item6) + + if storage.caveBot.forceRefill then + print("CaveBot[SupplyCheck]: User forced, going back on refill. Last round took: " .. round) + storage.caveBot.forceRefill = false + supplyRetries = 0 + missedChecks = 0 + return false + elseif storage.caveBot.backStop then + print("CaveBot[SupplyCheck]: User forced, going back to city and turning off CaveBot. Last round took: " .. round) + supplyRetries = 0 + missedChecks = 0 + return false + elseif storage.caveBot.backTrainers then + print("CaveBot[SupplyCheck]: User forced, going back to city, then on trainers. Last round took: " .. round) + supplyRetries = 0 + missedChecks = 0 + return false + elseif storage.caveBot.backOffline then + print("CaveBot[SupplyCheck]: User forced, going back to city, then on offline training. Last round took: " .. round) + supplyRetries = 0 + missedChecks = 0 + return false + elseif supplyRetries > (storage.extras.huntRoutes or 50) then + print("CaveBot[SupplyCheck]: Round limit reached, going back on refill. Last round took: " .. round) + supplyRetries = 0 + missedChecks = 0 + return false + elseif (supplies.imbues and player:getSkillLevel(11) == 0) then + print("CaveBot[SupplyCheck]: Imbues ran out. Going on refill. Last round took: " .. round) + supplyRetries = 0 + missedChecks = 0 + return false + elseif (supplies.staminaSwitch and stamina() < tonumber(supplies.staminaValue)) then + print("CaveBot[SupplyCheck]: Stamina ran out. Going on refill. Last round took: " .. round) + supplyRetries = 0 + missedChecks = 0 + return false + elseif (softCount < 1 and supplies.SoftBoots) then + print("CaveBot[SupplyCheck]: No soft boots left. Going on refill. Last round took: " .. round) + supplyRetries = 0 + missedChecks = 0 + return false + elseif (totalItem1 < tonumber(supplies.item1Min) and supplies.item1 > 100) then + print("CaveBot[SupplyCheck]: Not enough item: " .. supplies.item1 .. "(only " .. totalItem1 .. " left). Going on refill. Last round took: " .. round) + supplyRetries = 0 + missedChecks = 0 + return false + elseif (totalItem2 < tonumber(supplies.item2Min) and supplies.item2 > 100) then + print("CaveBot[SupplyCheck]: Not enough item: " .. supplies.item2 .. "(only " .. totalItem2 .. " left). Going on refill. Last round took: " .. round) + supplyRetries = 0 + missedChecks = 0 + return false + elseif (totalItem3 < tonumber(supplies.item3Min) and supplies.item3 > 100) then + print("CaveBot[SupplyCheck]: Not enough item: " .. supplies.item3 .. "(only " .. totalItem3 .. " left). Going on refill. Last round took: " .. round) + supplyRetries = 0 + missedChecks = 0 + return false + elseif (totalItem4 < tonumber(supplies.item4Min) and supplies.item4 > 100) then + print("CaveBot[SupplyCheck]: Not enough item: " .. supplies.item4 .. "(only " .. totalItem4 .. " left). Going on refill. Last round took: " .. round) + supplyRetries = 0 + missedChecks = 0 + return false + elseif (totalItem5 < tonumber(supplies.item5Min) and supplies.item5 > 100) then + print("CaveBot[SupplyCheck]: Not enough item: " .. supplies.item5 .. "(only " .. totalItem5 .. " left). Going on refill. Last round took: " .. round) + supplyRetries = 0 + missedChecks = 0 + return false + elseif (totalItem6 < tonumber(supplies.item6Min) and supplies.item6 > 100) then + print("CaveBot[SupplyCheck]: Not enough item: " .. supplies.item6 .. "(only " .. totalItem6 .. " left). Going on refill. Last round took: " .. round) + supplyRetries = 0 + missedChecks = 0 + return false + elseif (freecap() < tonumber(supplies.capValue) and supplies.capSwitch) then + print("CaveBot[SupplyCheck]: Not enough capacity. Going on refill. Last round took: " .. round) + supplyRetries = 0 + missedChecks = 0 + return false + elseif ForcedRefill then + print("CaveBot[SupplyCheck]: Forced refill, going back to city. Last round took: " .. round) + supplyRetries = 0 + missedChecks = 0 + return false + else + print("CaveBot[SupplyCheck]: Enough supplies. Hunting. Round (" .. supplyRetries .. "/" .. (storage.extras.huntRoutes or 50) .."). Last round took: " .. round) + supplyRetries = supplyRetries + 1 + missedChecks = 0 + return CaveBot.gotoLabel(label) + end + end) + + CaveBot.Editor.registerAction("supplycheck", "supply check", { + value=function() return "startHunt," .. posx() .. "," .. posy() .. "," .. posz() end, + title="Supply check label", + description="Insert here hunting start label", + validation=[[[^,]+,\d{1,5},\d{1,5},\d{1,2}$]] + }) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/tasker.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/tasker.lua new file mode 100644 index 0000000..959515e --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/tasker.lua @@ -0,0 +1,178 @@ +CaveBot.Extensions.Tasker = {} + +local dataValidationFailed = function() + print("CaveBot[Tasker]: data validation failed! incorrect data, check cavebot/tasker for more info") + return false +end + +-- miniconfig +local talkDelay = storage.extras.talkDelay +if not storage.caveBotTasker then + storage.caveBotTasker = { + inProgress = false, + monster = "", + taskName = "", + count = 0, + max = 0 + } +end + +local resetTaskData = function() + storage.caveBotTasker.inProgress = false + storage.caveBotTasker.monster = "" + storage.caveBotTasker.monster2 = "" + storage.caveBotTasker.taskName = "" + storage.caveBotTasker.count = 0 + storage.caveBotTasker.max = 0 +end + +CaveBot.Extensions.Tasker.setup = function() + CaveBot.registerAction("Tasker", "#FF0090", function(value, retries) + local taskName = "" + local monster = "" + local monster2 = "" + local count = 0 + local label1 = "" + local label2 = "" + local task + + local data = string.split(value, ",") + if not data or #data < 1 then + dataValidationFailed() + end + local marker = tonumber(data[1]) + + if not marker then + dataValidationFailed() + resetTaskData() + elseif marker == 1 then + if getNpcs(3) == 0 then + print("CaveBot[Tasker]: no NPC found in range! skipping") + return false + end + if #data ~= 4 and #data ~= 5 then + dataValidationFailed() + resetTaskData() + else + taskName = data[2]:lower():trim() + count = tonumber(data[3]:trim()) + monster = data[4]:lower():trim() + if #data == 5 then + monster2 = data[5]:lower():trim() + end + end + elseif marker == 2 then + if #data ~= 3 then + dataValidationFailed() + else + label1 = data[2]:lower():trim() + label2 = data[3]:lower():trim() + end + elseif marker == 3 then + if getNpcs(3) == 0 then + print("CaveBot[Tasker]: no NPC found in range! skipping") + return false + end + if #data ~= 1 then + dataValidationFailed() + end + end + + -- let's cover markers now + if marker == 1 then -- starting task + CaveBot.Conversation("hi", "task", taskName, "yes") + delay(talkDelay*4) + + storage.caveBotTasker.monster = monster + if monster2 then storage.caveBotTasker.monster2 = monster2 end + storage.caveBotTasker.taskName = taskName + storage.caveBotTasker.inProgress = true + storage.caveBotTasker.max = count + storage.caveBotTasker.count = 0 + + print("CaveBot[Tasker]: taken task for: " .. monster .. " x" .. count) + return true + elseif marker == 2 then -- only checking + if not storage.caveBotTasker.inProgress then + CaveBot.gotoLabel(label2) + print("CaveBot[Tasker]: there is no task in progress so going to take one.") + return true + end + + local max = storage.caveBotTasker.max + local count = storage.caveBotTasker.count + + if count >= max then + CaveBot.gotoLabel(label2) + print("CaveBot[Tasker]: task completed: " .. storage.caveBotTasker.taskName) + return true + else + CaveBot.gotoLabel(label1) + print("CaveBot[Tasker]: task in progress, left: " .. max - count .. " " .. storage.caveBotTasker.taskName) + return true + end + + + elseif marker == 3 then -- reporting task + CaveBot.Conversation("hi", "report", "task") + delay(talkDelay*3) + + resetTaskData() + print("CaveBot[Tasker]: task reported, done") + return true + end + + end) + + CaveBot.Editor.registerAction("tasker", "tasker", { + value=[[ There is 3 scenarios for this extension, as example we will use medusa: + + 1. start task, + parameters: + - scenario for extension: 1 + - task name in gryzzly adams: medusae + - monster count: 500 + - monster name to track: medusa + - optional, monster name 2: + 2. check status, + to be used on refill to decide whether to go back or spawn or go give task back + parameters: + - scenario for extension: 2 + - label if task in progress: skipTask + - label if task done: taskDone + 3. report task, + parameters: + - scenario for extension: 3 + + Strong suggestion, almost mandatory - USE POS CHECK to verify position! this module will only check if there is ANY npc in range! + + when begin remove all the text and leave just a single string of parameters + some examples: + + 2, skipReport, goReport + 3 + 1, drakens, 500, draken warmaster, draken spellweaver + 1, medusae, 500, medusa]], + title="Tasker", + multiline = true + }) +end + +local regex = "Loot of ([a-z])* ([a-z A-Z]*):" +local regex2 = "Loot of ([a-z A-Z]*):" +onTextMessage(function(mode, text) + -- if CaveBot.isOff() then return end + if not text:lower():find("loot of") then return end + if #regexMatch(text, regex) == 1 and #regexMatch(text, regex)[1] == 3 then + monster = regexMatch(text, regex)[1][3] + elseif #regexMatch(text, regex2) == 1 and #regexMatch(text, regex2)[1] == 2 then + monster = regexMatch(text, regex2)[1][2] + end + + local m1 = storage.caveBotTasker.monster + local m2 = storage.caveBotTasker.monster2 + + if monster == m1 or monster == m2 and storage.caveBotTasker.count then + storage.caveBotTasker.count = storage.caveBotTasker.count + 1 + end +end) diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/travel.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/travel.lua new file mode 100644 index 0000000..8e9d21e --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/travel.lua @@ -0,0 +1,40 @@ +CaveBot.Extensions.Travel = {} + +CaveBot.Extensions.Travel.setup = function() + CaveBot.registerAction("Travel", "#db5a5a", function(value, retries) + local data = string.split(value, ",") + if #data < 2 then + warn("CaveBot[Travel]: incorrect travel value!") + return false + end + + local npcName = data[1]:trim() + local dest = data[2]:trim() + + if retries > 5 then + print("CaveBot[Travel]: too many tries, can't travel") + return false + end + + local npc = getCreatureByName(npcName) + if not npc then + print("CaveBot[Travel]: NPC not found, can't travel") + return false + end + + if not CaveBot.ReachNPC(npcName) then + return "retry" + end + + CaveBot.Travel(dest) + delay(storage.extras.talkDelay*3) + print("CaveBot[Travel]: travel action finished") + return true + end) + + CaveBot.Editor.registerAction("travel", "travel", { + value="NPC name, city", + title="Travel", + description="NPC name, City name, delay in ms(default is 200ms)", + }) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/walking.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/walking.lua new file mode 100644 index 0000000..c8a7133 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/walking.lua @@ -0,0 +1,93 @@ +-- walking +local expectedDirs = {} +local isWalking = {} +local walkPath = {} +local walkPathIter = 0 + +CaveBot.resetWalking = function() + expectedDirs = {} + walkPath = {} + isWalking = false +end + +CaveBot.doWalking = function() + if CaveBot.Config.get("mapClick") then + return false + end + if #expectedDirs == 0 then + return false + end + if #expectedDirs >= 3 then + CaveBot.resetWalking() + end + local dir = walkPath[walkPathIter] + if dir then + g_game.walk(dir, false) + table.insert(expectedDirs, dir) + walkPathIter = walkPathIter + 1 + CaveBot.delay(CaveBot.Config.get("walkDelay") + player:getStepDuration(false, dir)) + return true + end + return false +end + +-- called when player position has been changed (step has been confirmed by server) +onPlayerPositionChange(function(newPos, oldPos) + if not oldPos or not newPos then return end + + local dirs = {{NorthWest, North, NorthEast}, {West, 8, East}, {SouthWest, South, SouthEast}} + local dir = dirs[newPos.y - oldPos.y + 2] + if dir then + dir = dir[newPos.x - oldPos.x + 2] + end + if not dir then + dir = 8 -- 8 is invalid dir, it's fine + end + + if not isWalking or not expectedDirs[1] then + -- some other walk action is taking place (for example use on ladder), wait + walkPath = {} + CaveBot.delay(CaveBot.Config.get("ping") + player:getStepDuration(false, dir) + 150) + return + end + + if expectedDirs[1] ~= dir then + if CaveBot.Config.get("mapClick") then + CaveBot.delay(CaveBot.Config.get("walkDelay") + player:getStepDuration(false, dir)) + else + CaveBot.delay(CaveBot.Config.get("mapClickDelay") + player:getStepDuration(false, dir)) + end + return + end + + table.remove(expectedDirs, 1) + if CaveBot.Config.get("mapClick") and #expectedDirs > 0 then + CaveBot.delay(CaveBot.Config.get("mapClickDelay") + player:getStepDuration(false, dir)) + end +end) + +CaveBot.walkTo = function(dest, maxDist, params) + local path = getPath(player:getPosition(), dest, maxDist, params) + if not path or not path[1] then + return false + end + local dir = path[1] + + if CaveBot.Config.get("mapClick") then + local ret = autoWalk(path) + if ret then + isWalking = true + expectedDirs = path + CaveBot.delay(CaveBot.Config.get("mapClickDelay") + math.max(CaveBot.Config.get("ping") + player:getStepDuration(false, dir), player:getStepDuration(false, dir) * 2)) + end + return ret + end + + g_game.walk(dir, false) + isWalking = true + walkPath = path + walkPathIter = 2 + expectedDirs = { dir } + CaveBot.delay(CaveBot.Config.get("walkDelay") + player:getStepDuration(false, dir)) + return true +end diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/withdraw.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/withdraw.lua new file mode 100644 index 0000000..da29053 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/cavebot/withdraw.lua @@ -0,0 +1,56 @@ +CaveBot.Extensions.Withdraw = {} + +CaveBot.Extensions.Withdraw.setup = function() + CaveBot.registerAction("withdraw", "#002FFF", function(value, retries) + -- validation + local data = string.split(value, ",") + if #data ~= 3 then + print("CaveBot[Withdraw]: incorrect data! skipping") + return false + end + + -- variables declaration + local source = tonumber(data[1]) + local id = tonumber(data[2]) + local amount = tonumber(data[3]) + + -- validation for correct values + if not id or not amount then + print("CaveBot[Withdraw]: incorrect id or amount! skipping") + return false + end + + -- check for retries + if retries > 100 then + print("CaveBot[Withdraw]: actions limit reached, proceeding") + for i, container in ipairs(getContainers()) do + if container:getName():lower():find("depot") or container:getName():lower():find("locker") then + g_game.close(container) + end + end + return true + end + + -- check for items + if itemAmount(id) >= amount then + print("CaveBot[Withdraw]: enough items, proceeding") + for i, container in ipairs(getContainers()) do + if container:getName():lower():find("depot") or container:getName():lower():find("locker") then + g_game.close(container) + end + end + return true + end + + statusMessage("[Withdraw] withdrawing item: " ..id.. " x"..amount) + CaveBot.WithdrawItem(id, amount, source) + CaveBot.PingDelay() + return "retry" + end) + + CaveBot.Editor.registerAction("withdraw", "withdraw", { + value="source,id,amount", + title="Withdraw Items", + description="index/inbox, item id and amount", + }) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/creature.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/creature.lua new file mode 100644 index 0000000..225bcec --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/creature.lua @@ -0,0 +1,99 @@ + +TargetBot.Creature = {} +TargetBot.Creature.configsCache = {} +TargetBot.Creature.cached = 0 + +TargetBot.Creature.resetConfigs = function() + TargetBot.targetList:destroyChildren() + TargetBot.Creature.resetConfigsCache() +end + +TargetBot.Creature.resetConfigsCache = function() + TargetBot.Creature.configsCache = {} + TargetBot.Creature.cached = 0 +end + +TargetBot.Creature.addConfig = function(config, focus) + if type(config) ~= 'table' or type(config.name) ~= 'string' then + return error("Invalid targetbot creature config (missing name)") + end + TargetBot.Creature.resetConfigsCache() + + if not config.regex then + config.regex = "" + for part in string.gmatch(config.name, "[^,]+") do + if config.regex:len() > 0 then + config.regex = config.regex .. "|" + end + config.regex = config.regex .. "^" .. part:trim():lower():gsub("%*", ".*"):gsub("%?", ".?") .. "$" + end + end + + local widget = UI.createWidget("TargetBotEntry", TargetBot.targetList) + widget:setText(config.name) + widget.value = config + + widget.onDoubleClick = function(entry) -- edit on double click + schedule(20, function() -- schedule to have correct focus + TargetBot.Creature.edit(entry.value, function(newConfig) + entry:setText(newConfig.name) + entry.value = newConfig + TargetBot.Creature.resetConfigsCache() + TargetBot.save() + end) + end) + end + + if focus then + widget:focus() + TargetBot.targetList:ensureChildVisible(widget) + end + return widget +end + +TargetBot.Creature.getConfigs = function(creature) + if not creature then return {} end + local name = creature:getName():trim():lower() + -- this function may be slow, so it will be using cache + if TargetBot.Creature.configsCache[name] then + return TargetBot.Creature.configsCache[name] + end + local configs = {} + for _, config in ipairs(TargetBot.targetList:getChildren()) do + if regexMatch(name, config.value.regex)[1] then + table.insert(configs, config.value) + end + end + if TargetBot.Creature.cached > 1000 then + TargetBot.Creature.resetConfigsCache() -- too big cache size, reset + end + TargetBot.Creature.configsCache[name] = configs -- add to cache + TargetBot.Creature.cached = TargetBot.Creature.cached + 1 + return configs +end + +TargetBot.Creature.calculateParams = function(creature, path) + local configs = TargetBot.Creature.getConfigs(creature) + local priority = 0 + local danger = 0 + local selectedConfig = nil + for _, config in ipairs(configs) do + local config_priority = TargetBot.Creature.calculatePriority(creature, config, path) + if config_priority > priority then + priority = config_priority + danger = TargetBot.Creature.calculateDanger(creature, config, path) + selectedConfig = config + end + end + return { + config = selectedConfig, + creature = creature, + danger = danger, + priority = priority + } +end + +TargetBot.Creature.calculateDanger = function(creature, config, path) + -- config is based on creature_editor + return config.danger +end diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/creature_attack.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/creature_attack.lua new file mode 100644 index 0000000..4c31644 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/creature_attack.lua @@ -0,0 +1,245 @@ +local targetBotLure = false +local targetCount = 0 +local delayValue = 0 +local lureMax = 0 +local anchorPosition = nil +local lastCall = now +local delayFrom = nil +local dynamicLureDelay = false + +function getWalkableTilesCount(position) + local count = 0 + + for i, tile in pairs(getNearTiles(position)) do + if tile:isWalkable() or tile:hasCreature() then + count = count + 1 + end + end + + return count +end + +function rePosition(minTiles) + minTiles = minTiles or 8 + if now - lastCall < 500 then return end + local pPos = player:getPosition() + local tiles = getNearTiles(pPos) + local playerTilesCount = getWalkableTilesCount(pPos) + local tilesTable = {} + + if playerTilesCount > minTiles then return end + for i, tile in ipairs(tiles) do + tilesTable[tile] = not tile:hasCreature() and tile:isWalkable() and getWalkableTilesCount(tile:getPosition()) or nil + end + + local best = 0 + local target = nil + for k,v in pairs(tilesTable) do + if v > best and v > playerTilesCount then + best = v + target = k:getPosition() + end + end + + if target then + lastCall = now + return CaveBot.GoTo(target, 0) + end +end + +TargetBot.Creature.attack = function(params, targets, isLooting) -- params {config, creature, danger, priority} + if player:isWalking() then + lastWalk = now + end + + local config = params.config + local creature = params.creature + + if g_game.getAttackingCreature() ~= creature then + g_game.attack(creature) + end + + if not isLooting then -- walk only when not looting + TargetBot.Creature.walk(creature, config, targets) + end + + -- attacks + local mana = player:getMana() + if config.useGroupAttack and config.groupAttackSpell:len() > 1 and mana > config.minManaGroup then + local creatures = g_map.getSpectatorsInRange(player:getPosition(), false, config.groupAttackRadius, config.groupAttackRadius) + local playersAround = false + local monsters = 0 + for _, creature in ipairs(creatures) do + if not creature:isLocalPlayer() and creature:isPlayer() and (not config.groupAttackIgnoreParty or creature:getShield() <= 2) then + playersAround = true + elseif creature:isMonster() then + monsters = monsters + 1 + end + end + if monsters >= config.groupAttackTargets and (not playersAround or config.groupAttackIgnorePlayers) then + if TargetBot.sayAttackSpell(config.groupAttackSpell, config.groupAttackDelay) then + return + end + end + end + + if config.useGroupAttackRune and config.groupAttackRune > 100 then + local creatures = g_map.getSpectatorsInRange(creature:getPosition(), false, config.groupRuneAttackRadius, config.groupRuneAttackRadius) + local playersAround = false + local monsters = 0 + for _, creature in ipairs(creatures) do + if not creature:isLocalPlayer() and creature:isPlayer() and (not config.groupAttackIgnoreParty or creature:getShield() <= 2) then + playersAround = true + elseif creature:isMonster() then + monsters = monsters + 1 + end + end + if monsters >= config.groupRuneAttackTargets and (not playersAround or config.groupAttackIgnorePlayers) then + if TargetBot.useAttackItem(config.groupAttackRune, 0, creature, config.groupRuneAttackDelay) then + return + end + end + end + if config.useSpellAttack and config.attackSpell:len() > 1 and mana > config.minMana then + if TargetBot.sayAttackSpell(config.attackSpell, config.attackSpellDelay) then + return + end + end + if config.useRuneAttack and config.attackRune > 100 then + if TargetBot.useAttackItem(config.attackRune, 0, creature, config.attackRuneDelay) then + return + end + end +end + +TargetBot.Creature.walk = function(creature, config, targets) + local cpos = creature:getPosition() + local pos = player:getPosition() + + local isTrapped = true + local pos = player:getPosition() + local dirs = {{-1,1}, {0,1}, {1,1}, {-1, 0}, {1, 0}, {-1, -1}, {0, -1}, {1, -1}} + for i=1,#dirs do + local tile = g_map.getTile({x=pos.x-dirs[i][1],y=pos.y-dirs[i][2],z=pos.z}) + if tile and tile:isWalkable(false) then + isTrapped = false + end + end + + -- data for external dynamic lure + if config.lureMin and config.lureMax and config.dynamicLure then + if config.lureMin >= targets then + targetBotLure = true + elseif targets >= config.lureMax then + targetBotLure = false + end + end + targetCount = targets + delayValue = config.lureDelay + + if config.lureMax then + lureMax = config.lureMax + end + + dynamicLureDelay = config.dynamicLureDelay + delayFrom = config.delayFrom + + -- luring + if config.closeLure and config.closeLureAmount <= getMonsters(1) then + return TargetBot.allowCaveBot(150) + end + if TargetBot.canLure() and (config.lure or config.lureCavebot or config.dynamicLure) and not (creature:getHealthPercent() < (storage.extras.killUnder or 30)) and not isTrapped then + if targetBotLure then + anchorPosition = nil + return TargetBot.allowCaveBot(150) + else + if targets < config.lureCount then + if config.lureCavebot then + anchorPosition = nil + return TargetBot.allowCaveBot(150) + else + local path = findPath(pos, cpos, 5, {ignoreNonPathable=true, precision=2}) + if path then + return TargetBot.walkTo(cpos, 10, {marginMin=5, marginMax=6, ignoreNonPathable=true}) + end + end + end + end + end + + local currentDistance = findPath(pos, cpos, 10, {ignoreCreatures=true, ignoreNonPathable=true, ignoreCost=true}) + if (not config.chase or #currentDistance == 1) and not config.avoidAttacks and not config.keepDistance and config.rePosition and (creature:getHealthPercent() >= storage.extras.killUnder) then + return rePosition(config.rePositionAmount or 6) + end + if ((storage.extras.killUnder > 1 and (creature:getHealthPercent() < storage.extras.killUnder)) or config.chase) and not config.keepDistance then + if #currentDistance > 1 then + return TargetBot.walkTo(cpos, 10, {ignoreNonPathable=true, precision=1}) + end + elseif config.keepDistance then + if not anchorPosition or distanceFromPlayer(anchorPosition) > config.anchorRange then + anchorPosition = pos + end + if #currentDistance ~= config.keepDistanceRange and #currentDistance ~= config.keepDistanceRange + 1 then + if config.anchor and anchorPosition and getDistanceBetween(pos, anchorPosition) <= config.anchorRange*2 then + return TargetBot.walkTo(cpos, 10, {ignoreNonPathable=true, marginMin=config.keepDistanceRange, marginMax=config.keepDistanceRange + 1, maxDistanceFrom={anchorPosition, config.anchorRange}}) + else + return TargetBot.walkTo(cpos, 10, {ignoreNonPathable=true, marginMin=config.keepDistanceRange, marginMax=config.keepDistanceRange + 1}) + end + end + end + + --target only movement + if config.avoidAttacks then + local diffx = cpos.x - pos.x + local diffy = cpos.y - pos.y + local candidates = {} + if math.abs(diffx) == 1 and diffy == 0 then + candidates = {{x=pos.x, y=pos.y-1, z=pos.z}, {x=pos.x, y=pos.y+1, z=pos.z}} + elseif diffx == 0 and math.abs(diffy) == 1 then + candidates = {{x=pos.x-1, y=pos.y, z=pos.z}, {x=pos.x+1, y=pos.y, z=pos.z}} + end + for _, candidate in ipairs(candidates) do + local tile = g_map.getTile(candidate) + if tile and tile:isWalkable() then + return TargetBot.walkTo(candidate, 2, {ignoreNonPathable=true}) + end + end + elseif config.faceMonster then + local diffx = cpos.x - pos.x + local diffy = cpos.y - pos.y + local candidates = {} + if diffx == 1 and diffy == 1 then + candidates = {{x=pos.x+1, y=pos.y, z=pos.z}, {x=pos.x, y=pos.y-1, z=pos.z}} + elseif diffx == -1 and diffy == 1 then + candidates = {{x=pos.x-1, y=pos.y, z=pos.z}, {x=pos.x, y=pos.y-1, z=pos.z}} + elseif diffx == -1 and diffy == -1 then + candidates = {{x=pos.x, y=pos.y-1, z=pos.z}, {x=pos.x-1, y=pos.y, z=pos.z}} + elseif diffx == 1 and diffy == -1 then + candidates = {{x=pos.x, y=pos.y-1, z=pos.z}, {x=pos.x+1, y=pos.y, z=pos.z}} + else + local dir = player:getDirection() + if diffx == 1 and dir ~= 1 then turn(1) + elseif diffx == -1 and dir ~= 3 then turn(3) + elseif diffy == 1 and dir ~= 2 then turn(2) + elseif diffy == -1 and dir ~= 0 then turn(0) + end + end + for _, candidate in ipairs(candidates) do + local tile = g_map.getTile(candidate) + if tile and tile:isWalkable() then + return TargetBot.walkTo(candidate, 2, {ignoreNonPathable=true}) + end + end + end +end + +onPlayerPositionChange(function(newPos, oldPos) + if CaveBot.isOff() then return end + if TargetBot.isOff() then return end + if not lureMax then return end + if storage.TargetBotDelayWhenPlayer then return end + if not dynamicLureDelay then return end + + if targetCount < (delayFrom or lureMax/2) or not target() then return end + CaveBot.delay(delayValue or 0) +end) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/creature_editor.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/creature_editor.lua new file mode 100644 index 0000000..37d6d00 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/creature_editor.lua @@ -0,0 +1,106 @@ +TargetBot.Creature.edit = function(config, callback) -- callback = function(newConfig) + config = config or {} + + local editor = UI.createWindow('TargetBotCreatureEditorWindow') + local values = {} -- (key, function returning value of key) + + editor.name:setText(config.name or "") + table.insert(values, {"name", function() return editor.name:getText() end}) + + local addScrollBar = function(id, title, min, max, defaultValue) + local widget = UI.createWidget('TargetBotCreatureEditorScrollBar', editor.content.left) + widget.scroll.onValueChange = function(scroll, value) + widget.text:setText(title .. ": " .. value) + end + widget.scroll:setRange(min, max) + if max-min > 1000 then + widget.scroll:setStep(100) + elseif max-min > 100 then + widget.scroll:setStep(10) + end + widget.scroll:setValue(config[id] or defaultValue) + widget.scroll.onValueChange(widget.scroll, widget.scroll:getValue()) + table.insert(values, {id, function() return widget.scroll:getValue() end}) + end + + local addTextEdit = function(id, title, defaultValue) + local widget = UI.createWidget('TargetBotCreatureEditorTextEdit', editor.content.right) + widget.text:setText(title) + widget.textEdit:setText(config[id] or defaultValue or "") + table.insert(values, {id, function() return widget.textEdit:getText() end}) + end + + local addCheckBox = function(id, title, defaultValue) + local widget = UI.createWidget('TargetBotCreatureEditorCheckBox', editor.content.right) + widget.onClick = function() + widget:setOn(not widget:isOn()) + end + widget:setText(title) + if config[id] == nil then + widget:setOn(defaultValue) + else + widget:setOn(config[id]) + end + table.insert(values, {id, function() return widget:isOn() end}) + end + + local addItem = function(id, title, defaultItem) + local widget = UI.createWidget('TargetBotCreatureEditorItem', editor.content.right) + widget.text:setText(title) + widget.item:setItemId(config[id] or defaultItem) + table.insert(values, {id, function() return widget.item:getItemId() end}) + end + + editor.cancel.onClick = function() + editor:destroy() + end + editor.onEscape = editor.cancel.onClick + + editor.ok.onClick = function() + local newConfig = {} + for _, value in ipairs(values) do + newConfig[value[1]] = value[2]() + end + if newConfig.name:len() < 1 then return end + + newConfig.regex = "" + for part in string.gmatch(newConfig.name, "[^,]+") do + if newConfig.regex:len() > 0 then + newConfig.regex = newConfig.regex .. "|" + end + newConfig.regex = newConfig.regex .. "^" .. part:trim():lower():gsub("%*", ".*"):gsub("%?", ".?") .. "$" + end + + editor:destroy() + callback(newConfig) + end + + -- values + addScrollBar("priority", "Priority", 0, 10, 1) + addScrollBar("danger", "Danger", 0, 10, 1) + addScrollBar("maxDistance", "Max distance", 1, 10, 10) + addScrollBar("keepDistanceRange", "Keep distance", 1, 5, 1) + addScrollBar("anchorRange", "Anchoring Range", 1, 10, 3) + addScrollBar("lureCount", "Classic Lure", 0, 5, 1) + addScrollBar("lureMin", "Dynamic lure min", 0, 29, 1) + addScrollBar("lureMax", "Dynamic lure max", 1, 30, 3) + addScrollBar("lureDelay", "Dynamic lure delay", 100, 1000, 250) + addScrollBar("delayFrom", "Start delay when monsters", 1, 29, 2) + addScrollBar("rePositionAmount", "Min tiles to rePosition", 0, 7, 5) + addScrollBar("closeLureAmount", "Close Pull Until", 0, 8, 3) + + addCheckBox("chase", "Chase", true) + addCheckBox("keepDistance", "Keep Distance", false) + addCheckBox("anchor", "Anchoring", false) + addCheckBox("dontLoot", "Don't loot", false) + addCheckBox("lure", "Lure", false) + addCheckBox("lureCavebot", "Lure using cavebot", false) + addCheckBox("faceMonster", "Face monsters", false) + addCheckBox("avoidAttacks", "Avoid wave attacks", false) + addCheckBox("dynamicLure", "Dynamic lure", false) + addCheckBox("dynamicLureDelay", "Dynamic lure delay", false) + addCheckBox("diamondArrows", "D-Arrows priority", false) + addCheckBox("rePosition", "rePosition to better tile", false) + addCheckBox("closeLure", "Close Pulling Monsters", false) + addCheckBox("rpSafe", "RP PVP SAFE - (DA)", false) +end diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/creature_editor.otui b/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/creature_editor.otui new file mode 100644 index 0000000..9570f87 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/creature_editor.otui @@ -0,0 +1,164 @@ +TargetBotCreatureEditorScrollBar < Panel + height: 28 + margin-top: 3 + + Label + id: text + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-align: center + + HorizontalScrollBar + id: scroll + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 3 + minimum: 0 + maximum: 10 + step: 1 + +TargetBotCreatureEditorTextEdit < Panel + height: 40 + margin-top: 7 + + Label + id: text + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-align: center + + TextEdit + id: textEdit + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 0 + maximum: 10 + step: 1 + +TargetBotCreatureEditorItem < Panel + height: 34 + margin-top: 7 + margin-left: 25 + margin-right: 25 + + Label + id: text + anchors.left: parent.left + anchors.verticalCenter: next.verticalCenter + + BotItem + id: item + anchors.top: parent.top + anchors.right: parent.right + + +TargetBotCreatureEditorCheckBox < BotSwitch + height: 20 + margin-top: 7 + +TargetBotCreatureEditorWindow < MainWindow + text: TargetBot creature editor + width: 500 + height: 425 + + $mobile: + height: 300 + + Label + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-align: center + !text: tr('You can use * (any characters) and ? (any character) in target name') + + Label + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + text-align: center + !text: tr('You can also enter multiple targets, separate them by ,') + + TextEdit + id: name + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-left: 90 + margin-top: 5 + + Label + anchors.verticalCenter: prev.verticalCenter + anchors.left: parent.left + text: Target name: + + VerticalScrollBar + id: contentScroll + anchors.top: name.bottom + anchors.right: parent.right + anchors.bottom: help.top + step: 28 + pixels-scroll: true + margin-right: -10 + margin-top: 5 + margin-bottom: 5 + + ScrollablePanel + id: content + anchors.top: name.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: help.top + vertical-scrollbar: contentScroll + margin-bottom: 10 + + Panel + id: left + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.horizontalCenter + margin-top: 5 + margin-left: 10 + margin-right: 10 + layout: + type: verticalBox + fit-children: true + + Panel + id: right + anchors.top: parent.top + anchors.left: parent.horizontalCenter + anchors.right: parent.right + margin-top: 5 + margin-left: 10 + margin-right: 10 + layout: + type: verticalBox + fit-children: true + + Button + id: help + !text: tr('Help & Tutorials') + anchors.bottom: parent.bottom + anchors.left: parent.left + width: 150 + @onClick: g_platform.openUrl("http://bot.otclient.ovh/") + + Button + id: ok + !text: tr('Ok') + anchors.bottom: parent.bottom + anchors.right: next.left + margin-right: 10 + width: 60 + + Button + id: cancel + !text: tr('Cancel') + anchors.bottom: parent.bottom + anchors.right: parent.right + width: 60 diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/creature_priority.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/creature_priority.lua new file mode 100644 index 0000000..813d3a6 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/creature_priority.lua @@ -0,0 +1,61 @@ +TargetBot.Creature.calculatePriority = function(creature, config, path) + -- config is based on creature_editor + local priority = 0 + local currentTarget = g_game.getAttackingCreature() + + -- extra priority if it's current target + if currentTarget == creature then + priority = priority + 1 + end + + -- check if distance is ok + if #path > config.maxDistance then + if config.rpSafe then + if currentTarget == creature then + g_game.cancelAttackAndFollow() -- if not, stop attack (pvp safe) + end + end + return priority + end + + -- add config priority + priority = priority + config.priority + + -- extra priority for close distance + local path_length = #path + if path_length == 1 then + priority = priority + 10 + elseif path_length <= 3 then + priority = priority + 5 + end + + -- extra priority for paladin diamond arrows + if config.diamondArrows then + local mobCount = getCreaturesInArea(creature:getPosition(), diamondArrowArea, 2) + priority = priority + (mobCount * 4) + + if config.rpSafe then + if getCreaturesInArea(creature:getPosition(), largeRuneArea, 3) > 0 then + if currentTarget == creature then + g_game.cancelAttackAndFollow() + end + return 0 -- pvp safe + end + end + end + + -- extra priority for low health + if config.chase and creature:getHealthPercent() < 30 then + priority = priority + 5 + elseif creature:getHealthPercent() < 20 then + priority = priority + 2.5 + elseif creature:getHealthPercent() < 40 then + priority = priority + 1.5 + elseif creature:getHealthPercent() < 60 then + priority = priority + 0.5 + elseif creature:getHealthPercent() < 80 then + priority = priority + 0.2 + end + + return priority +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/looting.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/looting.lua new file mode 100644 index 0000000..f93a266 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/looting.lua @@ -0,0 +1,325 @@ +TargetBot.Looting = {} +TargetBot.Looting.list = {} -- list of containers to loot + +local ui +local items = {} +local containers = {} +local itemsById = {} +local containersById = {} +local dontSave = false + +TargetBot.Looting.setup = function() + ui = UI.createWidget("TargetBotLootingPanel") + UI.Container(TargetBot.Looting.onItemsUpdate, true, nil, ui.items) + UI.Container(TargetBot.Looting.onContainersUpdate, true, nil, ui.containers) + ui.everyItem.onClick = function() + ui.everyItem:setOn(not ui.everyItem:isOn()) + TargetBot.save() + end + ui.maxDangerPanel.value.onTextChange = function() + local value = tonumber(ui.maxDangerPanel.value:getText()) + if not value then + ui.maxDangerPanel.value:setText(0) + end + if dontSave then return end + TargetBot.save() + end + ui.minCapacityPanel.value.onTextChange = function() + local value = tonumber(ui.minCapacityPanel.value:getText()) + if not value then + ui.minCapacityPanel.value:setText(0) + end + if dontSave then return end + TargetBot.save() + end +end + +TargetBot.Looting.onItemsUpdate = function() + if dontSave then return end + TargetBot.save() + TargetBot.Looting.updateItemsAndContainers() +end + +TargetBot.Looting.onContainersUpdate = function() + if dontSave then return end + TargetBot.save() + TargetBot.Looting.updateItemsAndContainers() +end + +TargetBot.Looting.update = function(data) + dontSave = true + TargetBot.Looting.list = {} + ui.items:setItems(data['items'] or {}) + ui.containers:setItems(data['containers'] or {}) + ui.everyItem:setOn(data['everyItem']) + ui.maxDangerPanel.value:setText(data['maxDanger'] or 10) + ui.minCapacityPanel.value:setText(data['minCapacity'] or 100) + TargetBot.Looting.updateItemsAndContainers() + dontSave = false + --vBot + vBot.lootConainers = {} + vBot.lootItems = {} + for i, item in ipairs(ui.containers:getItems()) do + table.insert(vBot.lootConainers, item['id']) + end + for i, item in ipairs(ui.items:getItems()) do + table.insert(vBot.lootItems, item['id']) + end +end + +TargetBot.Looting.save = function(data) + data['items'] = ui.items:getItems() + data['containers'] = ui.containers:getItems() + data['maxDanger'] = tonumber(ui.maxDangerPanel.value:getText()) + data['minCapacity'] = tonumber(ui.minCapacityPanel.value:getText()) + data['everyItem'] = ui.everyItem:isOn() +end + +TargetBot.Looting.updateItemsAndContainers = function() + items = ui.items:getItems() + containers = ui.containers:getItems() + itemsById = {} + containersById = {} + for i, item in ipairs(items) do + itemsById[item.id] = 1 + end + for i, container in ipairs(containers) do + containersById[container.id] = 1 + end +end + +local waitTill = 0 +local waitingForContainer = nil +local status = "" +local lastFoodConsumption = 0 + +TargetBot.Looting.getStatus = function() + return status +end + +TargetBot.Looting.process = function(targets, dangerLevel) + if (not items[1] and not ui.everyItem:isOn()) or not containers[1] then + status = "" + return false + end + if dangerLevel > tonumber(ui.maxDangerPanel.value:getText()) then + status = "High danger" + return false + end + if player:getFreeCapacity() < tonumber(ui.minCapacityPanel.value:getText()) then + status = "No cap" + TargetBot.Looting.list = {} + return false + end + local loot = storage.extras.lootLast and TargetBot.Looting.list[#TargetBot.Looting.list] or TargetBot.Looting.list[1] + if loot == nil then + status = "" + return false + end + + if waitTill > now then + return true + end + local containers = g_game.getContainers() + local lootContainers = TargetBot.Looting.getLootContainers(containers) + + -- check if there's container for loot and has empty space for it + if not lootContainers[1] then + -- there's no space, don't loot + status = "No space" + return false + end + + status = "Looting" + + for index, container in pairs(containers) do + if container.lootContainer then + TargetBot.Looting.lootContainer(lootContainers, container) + return true + end + end + + local pos = player:getPosition() + local dist = math.max(math.abs(pos.x-loot.pos.x), math.abs(pos.y-loot.pos.y)) + local maxRange = storage.extras.looting or 40 + if loot.tries > 30 or loot.pos.z ~= pos.z or dist > maxRange then + table.remove(TargetBot.Looting.list, storage.extras.lootLast and #TargetBot.Looting.list or 1) + return true + end + + local tile = g_map.getTile(loot.pos) + if dist >= 3 or not tile then + loot.tries = loot.tries + 1 + TargetBot.walkTo(loot.pos, 20, { ignoreNonPathable = true, precision = 2 }) + return true + end + + local container = tile:getTopUseThing() + if not container or not container:isContainer() then + table.remove(TargetBot.Looting.list, storage.extras.lootLast and #TargetBot.Looting.list or 1) + return true + end + + g_game.open(container) + waitTill = now + 1000 -- give it 1s to open + waitingForContainer = container:getId() + loot.tries = loot.tries + 10 + + return true +end + +TargetBot.Looting.getLootContainers = function(containers) + local lootContainers = {} + local openedContainersById = {} + local toOpen = nil + for index, container in pairs(containers) do + openedContainersById[container:getContainerItem():getId()] = 1 + if containersById[container:getContainerItem():getId()] and not container.lootContainer then + if container:getItemsCount() < container:getCapacity() or container:hasPages() then + table.insert(lootContainers, container) + else -- it's full, open next container if possible + for slot, item in ipairs(container:getItems()) do + if item:isContainer() and containersById[item:getId()] then + toOpen = {item, container} + break + end + end + end + end + end + if not lootContainers[1] then + if toOpen then + g_game.open(toOpen[1], toOpen[2]) + waitTill = now + 500 -- wait 0.5s + return lootContainers + end + -- check containers one more time, maybe there's any loot container + for index, container in pairs(containers) do + if not containersById[container:getContainerItem():getId()] and not container.lootContainer then + for slot, item in ipairs(container:getItems()) do + if item:isContainer() and containersById[item:getId()] then + g_game.open(item) + waitTill = now + 500 -- wait 0.5s + return lootContainers + end + end + end + end + -- can't find any lootContainer, let's check slots, maybe there's one + for slot = InventorySlotFirst, InventorySlotLast do + local item = getInventoryItem(slot) + if item and item:isContainer() and not openedContainersById[item:getId()] then + -- container which is not opened yet, let's open it + g_game.open(item) + waitTill = now + 500 -- wait 0.5s + return lootContainers + end + end + end + return lootContainers +end + +TargetBot.Looting.lootContainer = function(lootContainers, container) + -- loot items + local nextContainer = nil + for i, item in ipairs(container:getItems()) do + if item:isContainer() and not itemsById[item:getId()] then + nextContainer = item + elseif itemsById[item:getId()] or (ui.everyItem:isOn() and not item:isContainer()) then + item.lootTries = (item.lootTries or 0) + 1 + if item.lootTries < 5 then -- if can't be looted within 0.5s then skip it + return TargetBot.Looting.lootItem(lootContainers, item) + end + elseif storage.foodItems and storage.foodItems[1] and lastFoodConsumption + 5000 < now then + for _, food in ipairs(storage.foodItems) do + if item:getId() == food.id then + g_game.use(item) + lastFoodConsumption = now + return + end + end + end + end + + -- no more items to loot, open next container + if nextContainer then + nextContainer.lootTries = (nextContainer.lootTries or 0) + 1 + if nextContainer.lootTries < 2 then -- max 0.6s to open it + g_game.open(nextContainer, container) + waitTill = now + 300 -- give it 0.3s to open + waitingForContainer = nextContainer:getId() + return + end + end + + -- looting finished, remove container from list + container.lootContainer = false + g_game.close(container) + table.remove(TargetBot.Looting.list, storage.extras.lootLast and #TargetBot.Looting.list or 1) +end + +onTextMessage(function(mode, text) + if TargetBot.isOff() then return end + if #TargetBot.Looting.list == 0 then return end + if string.find(text:lower(), "you are not the owner") then -- if we are not the owners of corpse then its a waste of time to try to loot it + table.remove(TargetBot.Looting.list, storage.extras.lootLast and #TargetBot.Looting.list or 1) + end +end) + +TargetBot.Looting.lootItem = function(lootContainers, item) + if item:isStackable() then + local count = item:getCount() + for _, container in ipairs(lootContainers) do + for slot, citem in ipairs(container:getItems()) do + if item:getId() == citem:getId() and citem:getCount() < 100 then + g_game.move(item, container:getSlotPosition(slot - 1), count) + waitTill = now + 300 -- give it 0.3s to move item + return + end + end + end + end + + local container = lootContainers[1] + g_game.move(item, container:getSlotPosition(container:getItemsCount()), 1) + waitTill = now + 300 -- give it 0.3s to move item +end + +onContainerOpen(function(container, previousContainer) + if container:getContainerItem():getId() == waitingForContainer then + container.lootContainer = true + waitingForContainer = nil + end +end) + +onCreatureDisappear(function(creature) + if isInPz() then return end + if not TargetBot.isOn() then return end + if not creature:isMonster() then return end + local config = TargetBot.Creature.calculateParams(creature, {}) -- return {craeture, config, danger, priority} + if not config.config or config.config.dontLoot then + return + end + local pos = player:getPosition() + local mpos = creature:getPosition() + local name = creature:getName() + if pos.z ~= mpos.z or math.max(math.abs(pos.x-mpos.x), math.abs(pos.y-mpos.y)) > 6 then return end + schedule(20, function() -- check in 20ms if there's container (dead body) on that tile + if not containers[1] then return end + if TargetBot.Looting.list[20] then return end -- too many items to loot + local tile = g_map.getTile(mpos) + if not tile then return end + local container = tile:getTopUseThing() + if not container or not container:isContainer() then return end + if not findPath(player:getPosition(), mpos, 6, {ignoreNonPathable=true, ignoreCreatures=true, ignoreCost=true}) then return end + table.insert(TargetBot.Looting.list, {pos=mpos, creature=name, container=container:getId(), added=now, tries=0}) + + table.sort(TargetBot.Looting.list, function(a,b) + a.dist = distanceFromPlayer(a.pos) + b.dist = distanceFromPlayer(b.pos) + + return a.dist > b.dist + end) + container:setMarked('#000088') + end) +end) diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/looting.otui b/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/looting.otui new file mode 100644 index 0000000..aa973e3 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/looting.otui @@ -0,0 +1,69 @@ +TargetBotLootingPanel < Panel + layout: + type: verticalBox + fit-children: true + + HorizontalSeparator + margin-top: 5 + + Label + margin-top: 5 + text: Items to loot + text-align: center + + BotContainer + id: items + margin-top: 3 + + BotSwitch + id: everyItem + !text: tr("Loot every item") + margin-top: 2 + + Label + margin-top: 5 + text: Containers for loot + text-align: center + + BotContainer + id: containers + margin-top: 3 + height: 45 + + Panel + id: maxDangerPanel + height: 20 + margin-top: 5 + + BotTextEdit + id: value + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + margin-right: 6 + width: 80 + + Label + anchors.left: parent.left + anchors.verticalCenter: prev.verticalCenter + text: Max. danger: + margin-left: 5 + + Panel + id: minCapacityPanel + height: 20 + margin-top: 3 + + BotTextEdit + id: value + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + margin-right: 6 + width: 80 + + Label + anchors.left: parent.left + anchors.verticalCenter: prev.verticalCenter + text: Min. capacity: + margin-left: 5 \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/target.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/target.lua new file mode 100644 index 0000000..8bfb499 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/target.lua @@ -0,0 +1,328 @@ +local targetbotMacro = nil +local config = nil +local lastAction = 0 +local cavebotAllowance = 0 +local lureEnabled = true +local dangerValue = 0 +local looterStatus = "" + +-- ui +local configWidget = UI.Config() +local ui = UI.createWidget("TargetBotPanel") + +ui.list = ui.listPanel.list -- shortcut +TargetBot.targetList = ui.list +TargetBot.Looting.setup() + +ui.status.left:setText("Status:") +ui.status.right:setText("Off") +ui.target.left:setText("Target:") +ui.target.right:setText("-") +ui.config.left:setText("Config:") +ui.config.right:setText("-") +ui.danger.left:setText("Danger:") +ui.danger.right:setText("0") + +ui.editor.debug.onClick = function() + local on = ui.editor.debug:isOn() + ui.editor.debug:setOn(not on) + if on then + for _, spec in ipairs(getSpectators()) do + spec:clearText() + end + end +end + +local oldTibia = g_game.getClientVersion() < 960 + +-- main loop, controlled by config +targetbotMacro = macro(100, function() + local pos = player:getPosition() + local specs = g_map.getSpectatorsInRange(pos, false, 6, 6) -- 12x12 area + local creatures = 0 + for i, spec in ipairs(specs) do + if spec:isMonster() then + creatures = creatures + 1 + end + end + if creatures > 10 then -- if there are too many monsters around, limit area + creatures = g_map.getSpectatorsInRange(pos, false, 3, 3) -- 6x6 area + else + creatures = specs + end + local highestPriority = 0 + local dangerLevel = 0 + local targets = 0 + local highestPriorityParams = nil + for i, creature in ipairs(creatures) do + local hppc = creature:getHealthPercent() + if hppc and hppc > 0 then + local path = findPath(player:getPosition(), creature:getPosition(), 7, {ignoreLastCreature=true, ignoreNonPathable=true, ignoreCost=true, ignoreCreatures=true}) + if creature:isMonster() and (oldTibia or creature:getType() < 3) and path then + local params = TargetBot.Creature.calculateParams(creature, path) -- return {craeture, config, danger, priority} + dangerLevel = dangerLevel + params.danger + if params.priority > 0 then + targets = targets + 1 + if params.priority > highestPriority then + highestPriority = params.priority + highestPriorityParams = params + end + if ui.editor.debug:isOn() then + creature:setText(params.config.name .. "\n" .. params.priority) + end + end + end + end + end + + -- reset walking + TargetBot.walkTo(nil) + + -- looting + local looting = TargetBot.Looting.process(targets, dangerLevel) + local lootingStatus = TargetBot.Looting.getStatus() + looterStatus = TargetBot.Looting.getStatus() + dangerValue = dangerLevel + + ui.danger.right:setText(dangerLevel) + if highestPriorityParams and not isInPz() then + ui.target.right:setText(highestPriorityParams.creature:getName()) + ui.config.right:setText(highestPriorityParams.config.name) + TargetBot.Creature.attack(highestPriorityParams, targets, looting) + if lootingStatus:len() > 0 then + TargetBot.setStatus("Attack & " .. lootingStatus) + elseif cavebotAllowance > now then + TargetBot.setStatus("Luring using CaveBot") + else + TargetBot.setStatus("Attacking") + if not lureEnabled then + TargetBot.setStatus("Attacking (luring off)") + end + end + TargetBot.walk() + lastAction = now + return + end + + ui.target.right:setText("-") + ui.config.right:setText("-") + if looting then + TargetBot.walk() + lastAction = now + end + if lootingStatus:len() > 0 then + TargetBot.setStatus(lootingStatus) + else + TargetBot.setStatus("Waiting") + end +end) + +-- config, its callback is called immediately, data can be nil +config = Config.setup("targetbot_configs", configWidget, "json", function(name, enabled, data) + if not data then + ui.status.right:setText("Off") + return targetbotMacro.setOff() + end + TargetBot.Creature.resetConfigs() + for _, value in ipairs(data["targeting"] or {}) do + TargetBot.Creature.addConfig(value) + end + TargetBot.Looting.update(data["looting"] or {}) + + -- add configs + if enabled then + ui.status.right:setText("On") + else + ui.status.right:setText("Off") + end + + targetbotMacro.setOn(enabled) + targetbotMacro.delay = nil + lureEnabled = true +end) + +-- setup ui +ui.editor.buttons.add.onClick = function() + TargetBot.Creature.edit(nil, function(newConfig) + TargetBot.Creature.addConfig(newConfig, true) + TargetBot.save() + end) +end + +ui.editor.buttons.edit.onClick = function() + local entry = ui.list:getFocusedChild() + if not entry then return end + TargetBot.Creature.edit(entry.value, function(newConfig) + entry:setText(newConfig.name) + entry.value = newConfig + TargetBot.Creature.resetConfigsCache() + TargetBot.save() + end) +end + +ui.editor.buttons.remove.onClick = function() + local entry = ui.list:getFocusedChild() + if not entry then return end + entry:destroy() + TargetBot.Creature.resetConfigsCache() + TargetBot.save() +end + +-- public function, you can use them in your scripts +TargetBot.isActive = function() -- return true if attacking or looting takes place + return lastAction + 300 > now +end + +TargetBot.isCaveBotActionAllowed = function() + return cavebotAllowance > now +end + +TargetBot.setStatus = function(text) + return ui.status.right:setText(text) +end + +TargetBot.getStatus = function() + return ui.status.right:getText() +end + +TargetBot.isOn = function() + return config.isOn() +end + +TargetBot.isOff = function() + return config.isOff() +end + +TargetBot.setOn = function(val) + if val == false then + return TargetBot.setOff(true) + end + config.setOn() +end + +TargetBot.setOff = function(val) + if val == false then + return TargetBot.setOn(true) + end + config.setOff() +end + +TargetBot.getCurrentProfile = function() + return storage._configs.targetbot_configs.selected +end + +local botConfigName = modules.game_bot.contentsPanel.config:getCurrentOption().text +TargetBot.setCurrentProfile = function(name) + if not g_resources.fileExists("/bot/"..botConfigName.."/targetbot_configs/"..name..".json") then + return warn("there is no targetbot profile with that name!") + end + TargetBot.setOff() + storage._configs.targetbot_configs.selected = name + TargetBot.setOn() +end + +TargetBot.delay = function(value) + targetbotMacro.delay = now + value +end + +TargetBot.save = function() + local data = {targeting={}, looting={}} + for _, entry in ipairs(ui.list:getChildren()) do + table.insert(data.targeting, entry.value) + end + TargetBot.Looting.save(data.looting) + config.save(data) +end + +TargetBot.allowCaveBot = function(time) + cavebotAllowance = now + time +end + +TargetBot.disableLuring = function() + lureEnabled = false +end + +TargetBot.enableLuring = function() + lureEnabled = true +end + +TargetBot.Danger = function() + return dangerValue +end + +TargetBot.lootStatus = function() + return looterStatus +end + + +-- attacks +local lastSpell = 0 +local lastAttackSpell = 0 + +TargetBot.saySpell = function(text, delay) + if type(text) ~= 'string' or text:len() < 1 then return end + if not delay then delay = 500 end + if g_game.getProtocolVersion() < 1090 then + lastAttackSpell = now -- pause attack spells, healing spells are more important + end + if lastSpell + delay < now then + say(text) + lastSpell = now + return true + end + return false +end + +TargetBot.sayAttackSpell = function(text, delay) + if type(text) ~= 'string' or text:len() < 1 then return end + if not delay then delay = 2000 end + if lastAttackSpell + delay < now then + say(text) + lastAttackSpell = now + return true + end + return false +end + +local lastItemUse = 0 +local lastRuneAttack = 0 + +TargetBot.useItem = function(item, subType, target, delay) + if not delay then delay = 200 end + if lastItemUse + delay < now then + local thing = g_things.getThingType(item) + if not thing or not thing:isFluidContainer() then + subType = g_game.getClientVersion() >= 860 and 0 or 1 + end + if g_game.getClientVersion() < 780 then + local tmpItem = g_game.findPlayerItem(item, subType) + if not tmpItem then return end + g_game.useWith(tmpItem, target, subType) -- using item from bp + else + g_game.useInventoryItemWith(item, target, subType) -- hotkey + end + lastItemUse = now + end +end + +TargetBot.useAttackItem = function(item, subType, target, delay) + if not delay then delay = 2000 end + if lastRuneAttack + delay < now then + local thing = g_things.getThingType(item) + if not thing or not thing:isFluidContainer() then + subType = g_game.getClientVersion() >= 860 and 0 or 1 + end + if g_game.getClientVersion() < 780 then + local tmpItem = g_game.findPlayerItem(item, subType) + if not tmpItem then return end + g_game.useWith(tmpItem, target, subType) -- using item from bp + else + g_game.useInventoryItemWith(item, target, subType) -- hotkey + end + lastRuneAttack = now + end +end + +TargetBot.canLure = function() + return lureEnabled +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/target.otui b/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/target.otui new file mode 100644 index 0000000..6e0e4ea --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/target.otui @@ -0,0 +1,115 @@ +TargetBotEntry < Label + background-color: alpha + text-offset: 2 0 + focusable: true + + $focus: + background-color: #00000055 + +TargetBotDualLabel < Panel + height: 18 + margin-left: 3 + margin-right: 4 + + Label + id: left + anchors.top: parent.top + anchors.left: parent.left + text-auto-resize: true + + Label + id: right + anchors.top: parent.top + anchors.right: parent.right + text-auto-resize: true + +TargetBotPanel < Panel + layout: + type: verticalBox + fit-children: true + + HorizontalSeparator + margin-top: 2 + margin-bottom: 5 + + TargetBotDualLabel + id: status + TargetBotDualLabel + id: target + TargetBotDualLabel + id: config + TargetBotDualLabel + id: danger + + Panel + id: listPanel + height: 40 + + TextList + id: list + anchors.fill: parent + vertical-scrollbar: listScrollbar + margin-right: 15 + focusable: false + auto-focus: first + + VerticalScrollBar + id: listScrollbar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + pixels-scroll: true + step: 10 + + BotSwitch + id: configButton + @onClick: | + self:setOn(not self:isOn()) + self:getParent().listPanel:setHeight(self:isOn() and 100 or 40) + self:getParent().editor:setVisible(self:isOn()) + + $on: + text: Hide target editor + + $!on: + text: Show target editor + + Panel + id: editor + visible: false + layout: + type: verticalBox + fit-children: true + + Panel + id: buttons + height: 20 + margin-top: 2 + + Button + id: add + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + text: Add + width: 56 + + Button + id: edit + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + text: Edit + width: 56 + + Button + id: remove + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + text: Remove + width: 56 + + BotSwitch + id: debug + text: Show target priority diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/walking.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/walking.lua new file mode 100644 index 0000000..b256d6a --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/targetbot/walking.lua @@ -0,0 +1,28 @@ +local dest +local maxDist +local params + +TargetBot.walkTo = function(_dest, _maxDist, _params) + dest = _dest + maxDist = _maxDist + params = _params +end + +-- called every 100ms if targeting or looting is active +TargetBot.walk = function() + if not dest then return end + if player:isWalking() then return end + local pos = player:getPosition() + if pos.z ~= dest.z then return end + local dist = math.max(math.abs(pos.x-dest.x), math.abs(pos.y-dest.y)) + if params.precision and params.precision >= dist then return end + if params.marginMin and params.marginMax then + if dist >= params.marginMin and dist <= params.marginMax then + return + end + end + local path = getPath(pos, dest, maxDist, params) + if path then + walk(path[1]) + end +end diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/AttackBot.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/AttackBot.lua new file mode 100644 index 0000000..18a9831 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/AttackBot.lua @@ -0,0 +1,1248 @@ +setDefaultTab('main') +-- locales +local panelName = "AttackBot" +local currentSettings +local showSettings = false +local showItem = false +local category = 1 +local patternCategory = 1 +local pattern = 1 +local mainWindow + +-- label library + +local categories = { + "Targeted Spell (exori hur, exori flam, etc)", + "Area Rune (avalanche, great fireball, etc)", + "Targeted Rune (sudden death, icycle, etc)", + "Empowerment (utito tempo, etc)", + "Absolute Spell (exori, hells core, etc)", +} + +local patterns = { + -- targeted spells + { + "1 Sqm Range (exori ico)", + "2 Sqm Range", + "3 Sqm Range (strike spells)", + "4 Sqm Range (exori san)", + "5 Sqm Range (exori hur)", + "6 Sqm Range", + "7 Sqm Range (exori con)", + "8 Sqm Range", + "9 Sqm Range", + "10 Sqm Range" + }, + -- area runes + { + "Cross (explosion)", + "Bomb (fire bomb)", + "Ball (gfb, avalanche)" + }, + -- empowerment/targeted rune + { + "1 Sqm Range", + "2 Sqm Range", + "3 Sqm Range", + "4 Sqm Range", + "5 Sqm Range", + "6 Sqm Range", + "7 Sqm Range", + "8 Sqm Range", + "9 Sqm Range", + "10 Sqm Range", + }, + -- absolute + { + "Adjacent (exori, exori gran)", + "3x3 Wave (vis hur, tera hur)", + "Small Area (mas san, exori mas)", + "Medium Area (mas flam, mas frigo)", + "Large Area (mas vis, mas tera)", + "Short Beam (vis lux)", + "Large Beam (gran vis lux)", + "Sweep (exori min)", -- 8 + "Small Wave (gran frigo hur)", + "Big Wave (flam hur, frigo hur)", + "Huge Wave (gran flam hur)", + } +} + + -- spellPatterns[category][pattern][1 - normal, 2 - safe] +local spellPatterns = { + {}, -- blank, wont be used + -- Area Runes, + { + { -- cross + [[ + 010 + 111 + 010 + ]], + -- cross SAFE + [[ + 01110 + 01110 + 11111 + 11111 + 11111 + 01110 + 01110 + ]] + }, + { -- bomb + [[ + 111 + 111 + 111 + ]], + -- bomb SAFE + [[ + 11111 + 11111 + 11111 + 11111 + 11111 + ]] + }, + { -- ball + [[ + 0011100 + 0111110 + 1111111 + 1111111 + 1111111 + 0111110 + 0011100 + ]], + -- ball SAFE + [[ + 000111000 + 001111100 + 011111110 + 111111111 + 111111111 + 111111111 + 011111110 + 001111100 + 000111000 + ]] + }, + }, + {}, -- blank, wont be used + -- Absolute + { + {-- adjacent + [[ + 111 + 111 + 111 + ]], + -- adjacent SAFE + [[ + 11111 + 11111 + 11111 + 11111 + 11111 + ]] + }, + { -- 3x3 Wave + [[ + 0000NNN0000 + 0000NNN0000 + 0000NNN0000 + 00000N00000 + WWW00N00EEE + WWWWW0EEEEE + WWW00S00EEE + 00000S00000 + 0000SSS0000 + 0000SSS0000 + 0000SSS0000 + ]], + -- 3x3 Wave SAFE + [[ + 0000NNNNN0000 + 0000NNNNN0000 + 0000NNNNN0000 + 0000NNNNN0000 + WWWW0NNN0EEEE + WWWWWNNNEEEEE + WWWWWW0EEEEEE + WWWWWSSSEEEEE + WWWW0SSS0EEEE + 0000SSSSS0000 + 0000SSSSS0000 + 0000SSSSS0000 + 0000SSSSS0000 + ]] + }, + { -- small area + [[ + 0011100 + 0111110 + 1111111 + 1111111 + 1111111 + 0111110 + 0011100 + ]], + -- small area SAFE + [[ + 000111000 + 001111100 + 011111110 + 111111111 + 111111111 + 111111111 + 011111110 + 001111100 + 000111000 + ]] + }, + { -- medium area + [[ + 00000100000 + 00011111000 + 00111111100 + 01111111110 + 01111111110 + 11111111111 + 01111111110 + 01111111110 + 00111111100 + 00001110000 + 00000100000 + ]], + -- medium area SAFE + [[ + 0000011100000 + 0000111110000 + 0001111111000 + 0011111111100 + 0111111111110 + 0111111111110 + 1111111111111 + 0111111111110 + 0111111111110 + 0011111111100 + 0001111111000 + 0000111110000 + 0000011100000 + ]] + }, + { -- large area + [[ + 0000001000000 + 0000011100000 + 0000111110000 + 0001111111000 + 0011111111100 + 0111111111110 + 1111111111111 + 0111111111110 + 0011111111100 + 0001111111000 + 0000111110000 + 0000011100000 + 0000001000000 + ]], + -- large area SAFE + [[ + 000000010000000 + 000000111000000 + 000001111100000 + 000011111110000 + 000111111111000 + 001111111111100 + 011111111111110 + 111111111111111 + 011111111111110 + 001111111111100 + 000111111111000 + 000011111110000 + 000001111100000 + 000000111000000 + 000000010000000 + ]] + }, + { -- short beam + [[ + 00000N00000 + 00000N00000 + 00000N00000 + 00000N00000 + 00000N00000 + WWWWW0EEEEE + 00000S00000 + 00000S00000 + 00000S00000 + 00000S00000 + 00000S00000 + ]], + -- short beam SAFE + [[ + 00000NNN00000 + 00000NNN00000 + 00000NNN00000 + 00000NNN00000 + 00000NNN00000 + WWWWWNNNEEEEE + WWWWWW0EEEEEE + 00000SSS00000 + 00000SSS00000 + 00000SSS00000 + 00000SSS00000 + 00000SSS00000 + 00000SSS00000 + ]] + }, + { -- large beam + [[ + 0000000N0000000 + 0000000N0000000 + 0000000N0000000 + 0000000N0000000 + 0000000N0000000 + 0000000N0000000 + 0000000N0000000 + WWWWWWW0EEEEEEE + 0000000S0000000 + 0000000S0000000 + 0000000S0000000 + 0000000S0000000 + 0000000S0000000 + 0000000S0000000 + 0000000S0000000 + ]], + -- large beam SAFE + [[ + 0000000NNN0000000 + 0000000NNN0000000 + 0000000NNN0000000 + 0000000NNN0000000 + 0000000NNN0000000 + 0000000NNN0000000 + 0000000NNN0000000 + WWWWWWWNNNEEEEEEE + WWWWWWWW0EEEEEEEE + WWWWWWWSSSEEEEEEE + 0000000SSS0000000 + 0000000SSS0000000 + 0000000SSS0000000 + 0000000SSS0000000 + 0000000SSS0000000 + 0000000SSS0000000 + 0000000SSS0000000 + ]], + }, + {}, -- sweep, wont be used + { -- small wave + [[ + 00NNN00 + 00NNN00 + WW0N0EE + WWW0EEE + WW0S0EE + 00SSS00 + 00SSS00 + ]], + -- small wave SAFE + [[ + 00NNNNN00 + 00NNNNN00 + WWNNNNNEE + WWWWNEEEE + WWWW0EEEE + WWWWSEEEE + WWSSSSSEE + 00SSSSS00 + 00SSSSS00 + ]] + }, + { -- large wave + [[ + 000NNNNN000 + 000NNNNN000 + 0000NNN0000 + WW00NNN00EE + WWWW0N0EEEE + WWWWW0EEEEE + WWWW0S0EEEE + WW00SSS00EE + 0000SSS0000 + 000SSSSS000 + 000SSSSS000 + ]], + [[ + 000NNNNNNN000 + 000NNNNNNN000 + 000NNNNNNN000 + WWWWNNNNNEEEE + WWWWNNNNNEEEE + WWWWWNNNEEEEE + WWWWWW0EEEEEE + WWWWWSSSEEEEE + WWWWSSSSSEEEE + WWWWSSSSSEEEE + 000SSSSSSS000 + 000SSSSSSS000 + 000SSSSSSS000 + ]] + }, + { -- huge wave + [[ + 0000NNNNN0000 + 0000NNNNN0000 + 00000NNN00000 + 00000NNN00000 + WW0000N0000EE + WWWW00N00EEEE + WWWWWW0EEEEEE + WWWW00S00EEEE + WW0000S0000EE + 00000SSS00000 + 00000SSS00000 + 0000SSSSS0000 + 0000SSSSS0000 + ]], + [[ + 0000000NNN0000000 + 0000000NNN0000000 + 0000000NNN0000000 + 0000000NNN0000000 + 0000000NNN0000000 + 0000000NNN0000000 + 0000000NNN0000000 + WWWWWWWNNNEEEEEEE + WWWWWWWW0EEEEEEEE + WWWWWWWSSSEEEEEEE + 0000000SSS0000000 + 0000000SSS0000000 + 0000000SSS0000000 + 0000000SSS0000000 + 0000000SSS0000000 + 0000000SSS0000000 + 0000000SSS0000000 + ]] + } + } +} + +-- direction patterns +local ek = (voc() == 1 or voc() == 11) and true + +local posN = ek and [[ + 111 + 000 + 000 +]] or [[ + 00011111000 + 00011111000 + 00011111000 + 00011111000 + 00000100000 + 00000000000 + 00000000000 + 00000000000 + 00000000000 + 00000000000 + 00000000000 +]] + +local posE = ek and [[ + 001 + 001 + 001 +]] or [[ + 00000000000 + 00000000000 + 00000000000 + 00000001111 + 00000001111 + 00000011111 + 00000001111 + 00000001111 + 00000000000 + 00000000000 + 00000000000 +]] +local posS = ek and [[ + 000 + 000 + 111 +]] or [[ + 00000000000 + 00000000000 + 00000000000 + 00000000000 + 00000000000 + 00000000000 + 00000100000 + 00011111000 + 00011111000 + 00011111000 + 00011111000 +]] +local posW = ek and [[ + 100 + 100 + 100 +]] or [[ + 00000000000 + 00000000000 + 00000000000 + 11110000000 + 11110000000 + 11111000000 + 11110000000 + 11110000000 + 00000000000 + 00000000000 + 00000000000 +]] + +-- AttackBotConfig +-- create blank profiles +if not AttackBotConfig[panelName] or not AttackBotConfig[panelName][1] or #AttackBotConfig[panelName] ~= 5 then + AttackBotConfig[panelName] = { + [1] = { + enabled = false, + attackTable = {}, + ignoreMana = true, + Kills = false, + Rotate = false, + name = "Profile #1", + Cooldown = true, + Visible = true, + pvpMode = false, + KillsAmount = 1, + PvpSafe = true, + BlackListSafe = false, + AntiRsRange = 5 + }, + [2] = { + enabled = false, + attackTable = {}, + ignoreMana = true, + Kills = false, + Rotate = false, + name = "Profile #2", + Cooldown = true, + Visible = true, + pvpMode = false, + KillsAmount = 1, + PvpSafe = true, + BlackListSafe = false, + AntiRsRange = 5 + }, + [3] = { + enabled = false, + attackTable = {}, + ignoreMana = true, + Kills = false, + Rotate = false, + name = "Profile #3", + Cooldown = true, + Visible = true, + pvpMode = false, + KillsAmount = 1, + PvpSafe = true, + BlackListSafe = false, + AntiRsRange = 5 + }, + [4] = { + enabled = false, + attackTable = {}, + ignoreMana = true, + Kills = false, + Rotate = false, + name = "Profile #4", + Cooldown = true, + Visible = true, + pvpMode = false, + KillsAmount = 1, + PvpSafe = true, + BlackListSafe = false, + AntiRsRange = 5 + }, + [5] = { + enabled = false, + attackTable = {}, + ignoreMana = true, + Kills = false, + Rotate = false, + name = "Profile #5", + Cooldown = true, + Visible = true, + pvpMode = false, + KillsAmount = 1, + PvpSafe = true, + BlackListSafe = false, + AntiRsRange = 5 + }, + } +end + +if not AttackBotConfig.currentBotProfile or AttackBotConfig.currentBotProfile == 0 or AttackBotConfig.currentBotProfile > 5 then + AttackBotConfig.currentBotProfile = 1 +end + +-- create panel UI +ui = UI.createWidget("AttackBotBotPanel") + +-- finding correct table, manual unfortunately +local setActiveProfile = function() + local n = AttackBotConfig.currentBotProfile + currentSettings = AttackBotConfig[panelName][n] +end +setActiveProfile() + +if not currentSettings.AntiRsRange then + currentSettings.AntiRsRange = 5 +end + +local setProfileName = function() + ui.name:setText(currentSettings.name) +end + +-- small UI elements +ui.title.onClick = function(widget) + currentSettings.enabled = not currentSettings.enabled + widget:setOn(currentSettings.enabled) + vBotConfigSave("atk") +end + +ui.settings.onClick = function(widget) + mainWindow:show() + mainWindow:raise() + mainWindow:focus() +end + + mainWindow = UI.createWindow("AttackBotWindow") + mainWindow:hide() + + local panel = mainWindow.mainPanel + local settingsUI = mainWindow.settingsPanel + + mainWindow.onVisibilityChange = function(widget, visible) + if not visible then + currentSettings.attackTable = {} + for i, child in ipairs(panel.entryList:getChildren()) do + table.insert(currentSettings.attackTable, child.params) + end + vBotConfigSave("atk") + end + end + + -- main panel + + -- functions + function toggleSettings() + panel:setVisible(not showSettings) + mainWindow.shooterLabel:setVisible(not showSettings) + settingsUI:setVisible(showSettings) + mainWindow.settingsLabel:setVisible(showSettings) + mainWindow.settings:setText(showSettings and "Back" or "Settings") + end + toggleSettings() + + mainWindow.settings.onClick = function() + showSettings = not showSettings + toggleSettings() + end + + function toggleItem() + panel.monsters:setWidth(showItem and 405 or 341) + panel.itemId:setVisible(showItem) + panel.spellName:setVisible(not showItem) + end + toggleItem() + + function setCategoryText() + panel.category.description:setText(categories[category]) + end + setCategoryText() + + function setPatternText() + panel.range.description:setText(patterns[patternCategory][pattern]) + end + setPatternText() + + -- in/de/crementation buttons + panel.previousCategory.onClick = function() + if category == 1 then + category = #categories + else + category = category - 1 + end + + showItem = (category == 2 or category == 3) and true or false + patternCategory = category == 4 and 3 or category == 5 and 4 or category + pattern = 1 + toggleItem() + setPatternText() + setCategoryText() + end + panel.nextCategory.onClick = function() + if category == #categories then + category = 1 + else + category = category + 1 + end + + showItem = (category == 2 or category == 3) and true or false + patternCategory = category == 4 and 3 or category == 5 and 4 or category + pattern = 1 + toggleItem() + setPatternText() + setCategoryText() + end + panel.previousSource.onClick = function() + warn("[AttackBot] TODO, reserved for future use.") + end + panel.nextSource.onClick = function() + warn("[AttackBot] TODO, reserved for future use.") + end + panel.previousRange.onClick = function() + local t = patterns[patternCategory] + if pattern == 1 then + pattern = #t + else + pattern = pattern - 1 + end + setPatternText() + end + panel.nextRange.onClick = function() + local t = patterns[patternCategory] + if pattern == #t then + pattern = 1 + else + pattern = pattern + 1 + end + setPatternText() + end + -- eo in/de/crementation + + ------- [[core table function]] ------- + function setupWidget(widget) + local params = widget.params + + widget:setText(params.description) + if params.itemId > 0 then + widget.spell:setVisible(false) + widget.id:setVisible(true) + widget.id:setItemId(params.itemId) + end + widget:setTooltip(params.tooltip) + widget.remove.onClick = function() + panel.up:setEnabled(false) + panel.down:setEnabled(false) + widget:destroy() + end + widget.enabled:setChecked(params.enabled) + widget.enabled.onClick = function() + params.enabled = not params.enabled + widget.enabled:setChecked(params.enabled) + end + -- will serve as edit + widget.onDoubleClick = function(widget) + panel.manaPercent:setValue(params.mana) + panel.creatures:setValue(params.count) + panel.minHp:setValue(params.minHp) + panel.maxHp:setValue(params.maxHp) + panel.cooldown:setValue(params.cooldown) + showItem = params.itemId > 100 and true or false + panel.itemId:setItemId(params.itemId) + panel.spellName:setText(params.spell or "") + panel.orMore:setChecked(params.orMore) + toggleItem() + category = params.category + patternCategory = params.patternCategory + pattern = params.pattern + setPatternText() + setCategoryText() + widget:destroy() + end + widget.onClick = function(widget) + if #panel.entryList:getChildren() == 1 then + panel.up:setEnabled(false) + panel.down:setEnabled(false) + elseif panel.entryList:getChildIndex(widget) == 1 then + panel.up:setEnabled(false) + panel.down:setEnabled(true) + elseif panel.entryList:getChildIndex(widget) == panel.entryList:getChildCount() then + panel.up:setEnabled(true) + panel.down:setEnabled(false) + else + panel.up:setEnabled(true) + panel.down:setEnabled(true) + end + end + end + + + -- refreshing values + function refreshAttacks() + if not currentSettings.attackTable then return end + + panel.entryList:destroyChildren() + for i, entry in pairs(currentSettings.attackTable) do + local label = UI.createWidget("AttackEntry", panel.entryList) + label.params = entry + setupWidget(label) + end + end + refreshAttacks() + panel.up:setEnabled(false) + panel.down:setEnabled(false) + + -- adding values + panel.addEntry.onClick = function(wdiget) + -- first variables + local creatures = panel.monsters:getText():lower() + local monsters = (creatures:len() == 0 or creatures == "*" or creatures == "monster names") and true or string.split(creatures, ",") + local mana = panel.manaPercent:getValue() + local count = panel.creatures:getValue() + local minHp = panel.minHp:getValue() + local maxHp = panel.maxHp:getValue() + local cooldown = panel.cooldown:getValue() + local itemId = panel.itemId:getItemId() + local spell = panel.spellName:getText() + local tooltip = monsters ~= true and creatures + local orMore = panel.orMore:isChecked() + + -- validation + if showItem and itemId < 100 then + return warn("[AttackBot]: please fill item ID!") + elseif not showItem and (spell:lower() == "spell name" or spell:len() == 0) then + return warn("[AttackBot]: please fill spell name!") + end + + local regex = patternCategory ~= 1 and [[^[^\(]+]] or [[^[^R]+]] + local type = regexMatch(patterns[patternCategory][pattern], regex)[1][1]:trim() + regex = [[^[^ ]+]] + local categoryName = regexMatch(categories[category], regex)[1][1]:trim():lower() + local specificMonsters = monsters == true and "Any Creatures" or "Creatures" + local attackType = showItem and "rune "..itemId or spell + + local countDescription = orMore and count.."+" or count + + local params = { + creatures = creatures, + monsters = monsters, + mana = mana, + count = count, + minHp = minHp, + maxHp = maxHp, + cooldown = cooldown, + itemId = itemId, + spell = spell, + enabled = true, + category = category, + patternCategory = patternCategory, + pattern = pattern, + tooltip = tooltip, + orMore = orMore, + description = '['..type..'] '..countDescription.. ' '..specificMonsters..': '..attackType..', '..categoryName..' ('..minHp..'%-'..maxHp..'%)' + } + + local label = UI.createWidget("AttackEntry", panel.entryList) + label.params = params + setupWidget(label) + resetFields() + end + + -- moving values + -- up + panel.up.onClick = function(widget) + local focused = panel.entryList:getFocusedChild() + local n = panel.entryList:getChildIndex(focused) + + if n-1 == 1 then + widget:setEnabled(false) + end + panel.down:setEnabled(true) + panel.entryList:moveChildToIndex(focused, n-1) + panel.entryList:ensureChildVisible(focused) + end + -- down + panel.down.onClick = function(widget) + local focused = panel.entryList:getFocusedChild() + local n = panel.entryList:getChildIndex(focused) + + if n + 1 == panel.entryList:getChildCount() then + widget:setEnabled(false) + end + panel.up:setEnabled(true) + panel.entryList:moveChildToIndex(focused, n+1) + panel.entryList:ensureChildVisible(focused) + end + + -- [[settings panel]] -- + settingsUI.profileName.onTextChange = function(widget, text) + currentSettings.name = text + setProfileName() + end + settingsUI.IgnoreMana.onClick = function(widget) + currentSettings.ignoreMana = not currentSettings.ignoreMana + settingsUI.IgnoreMana:setChecked(currentSettings.ignoreMana) + end + settingsUI.Rotate.onClick = function(widget) + currentSettings.Rotate = not currentSettings.Rotate + settingsUI.Rotate:setChecked(currentSettings.Rotate) + end + settingsUI.Kills.onClick = function(widget) + currentSettings.Kills = not currentSettings.Kills + settingsUI.Kills:setChecked(currentSettings.Kills) + end + settingsUI.Cooldown.onClick = function(widget) + currentSettings.Cooldown = not currentSettings.Cooldown + settingsUI.Cooldown:setChecked(currentSettings.Cooldown) + end + settingsUI.Visible.onClick = function(widget) + currentSettings.Visible = not currentSettings.Visible + settingsUI.Visible:setChecked(currentSettings.Visible) + end + settingsUI.PvpMode.onClick = function(widget) + currentSettings.pvpMode = not currentSettings.pvpMode + settingsUI.PvpMode:setChecked(currentSettings.pvpMode) + end + settingsUI.PvpSafe.onClick = function(widget) + currentSettings.PvpSafe = not currentSettings.PvpSafe + settingsUI.PvpSafe:setChecked(currentSettings.PvpSafe) + end + settingsUI.Training.onClick = function(widget) + currentSettings.Training = not currentSettings.Training + settingsUI.Training:setChecked(currentSettings.Training) + end + settingsUI.BlackListSafe.onClick = function(widget) + currentSettings.BlackListSafe = not currentSettings.BlackListSafe + settingsUI.BlackListSafe:setChecked(currentSettings.BlackListSafe) + end + settingsUI.KillsAmount.onValueChange = function(widget, value) + currentSettings.KillsAmount = value + end + settingsUI.AntiRsRange.onValueChange = function(widget, value) + currentSettings.AntiRsRange = value + end + + + -- window elements + mainWindow.closeButton.onClick = function() + showSettings = false + toggleSettings() + resetFields() + mainWindow:hide() + end + + -- core functions + function resetFields() + showItem = false + toggleItem() + pattern = 1 + patternCategory = 1 + category = 1 + setPatternText() + setCategoryText() + panel.manaPercent:setText(1) + panel.creatures:setText(1) + panel.minHp:setValue(0) + panel.maxHp:setValue(100) + panel.cooldown:setText(1) + panel.monsters:setText("monster names") + panel.itemId:setItemId(0) + panel.spellName:setText("spell name") + panel.orMore:setChecked(false) + end + resetFields() + + function loadSettings() + -- BOT panel + ui.title:setOn(currentSettings.enabled) + setProfileName() + -- main panel + refreshAttacks() + -- settings + settingsUI.profileName:setText(currentSettings.name) + settingsUI.Visible:setChecked(currentSettings.Visible) + settingsUI.Cooldown:setChecked(currentSettings.Cooldown) + settingsUI.PvpMode:setChecked(currentSettings.pvpMode) + settingsUI.PvpSafe:setChecked(currentSettings.PvpSafe) + settingsUI.BlackListSafe:setChecked(currentSettings.BlackListSafe) + settingsUI.AntiRsRange:setValue(currentSettings.AntiRsRange) + settingsUI.IgnoreMana:setChecked(currentSettings.ignoreMana) + settingsUI.Rotate:setChecked(currentSettings.Rotate) + settingsUI.Kills:setChecked(currentSettings.Kills) + settingsUI.KillsAmount:setValue(currentSettings.KillsAmount) + settingsUI.Training:setChecked(currentSettings.Training) + end + loadSettings() + + local activeProfileColor = function() + for i=1,5 do + if i == AttackBotConfig.currentBotProfile then + ui[i]:setColor("green") + else + ui[i]:setColor("white") + end + end + end + activeProfileColor() + + local profileChange = function() + setActiveProfile() + activeProfileColor() + loadSettings() + resetFields() + vBotConfigSave("atk") + end + + for i=1,5 do + local button = ui[i] + button.onClick = function() + AttackBotConfig.currentBotProfile = i + profileChange() + end + end + + -- public functions + AttackBot = {} -- global table + + AttackBot.isOn = function() + return currentSettings.enabled + end + + AttackBot.isOff = function() + return not currentSettings.enabled + end + + AttackBot.setOff = function() + currentSettings.enabled = false + ui.title:setOn(currentSettings.enabled) + vBotConfigSave("atk") + end + + AttackBot.setOn = function() + currentSettings.enabled = true + ui.title:setOn(currentSettings.enabled) + vBotConfigSave("atk") + end + + AttackBot.getActiveProfile = function() + return AttackBotConfig.currentBotProfile -- returns number 1-5 + end + + AttackBot.setActiveProfile = function(n) + if not n or not tonumber(n) or n < 1 or n > 5 then + return error("[AttackBot] wrong profile parameter! should be 1 to 5 is " .. n) + else + AttackBotConfig.currentBotProfile = n + profileChange() + end + end + + AttackBot.show = function() + mainWindow:show() + mainWindow:raise() + mainWindow:focus() + end + + +-- otui covered, now support functions +function getPattern(category, pattern, safe) + safe = safe and 2 or 1 + + return spellPatterns[category][pattern][safe] +end + + +function getMonstersInArea(category, posOrCreature, pattern, minHp, maxHp, safePattern, monsterNamesTable) + -- monsterNamesTable can be nil + local monsters = 0 + local t = {} + if monsterNamesTable == true or not monsterNamesTable then + t = {} + else + t = monsterNamesTable + end + + if safePattern then + for i, spec in pairs(getSpectators(posOrCreature, safePattern)) do + if spec ~= player and (spec:isPlayer() and not spec:isPartyMember()) then + return 0 + end + end + end + + if category == 1 or category == 3 or category == 4 then + for i, spec in pairs(getSpectators()) do + local specHp = spec:getHealthPercent() + local name = spec:getName():lower() + monsters = spec:isMonster() and specHp >= minHp and specHp <= maxHp and (#t == 0 or table.find(t, name)) and + (g_game.getClientVersion() < 960 or spec:getType() < 3) and monsters + 1 or monsters + end + return monsters + end + + for i, spec in pairs(getSpectators(posOrCreature, pattern)) do + if spec ~= player then + local specHp = spec:getHealthPercent() + local name = spec:getName():lower() + monsters = spec:isMonster() and specHp >= minHp and specHp <= maxHp and (#t == 0 or table.find(t, name)) and + (g_game.getClientVersion() < 960 or spec:getType() < 3) and monsters + 1 or monsters + end + end + + return monsters +end + +-- for area runes only +-- should return valid targets number (int) and position +function getBestTileByPattern(pattern, minHp, maxHp, safePattern, monsterNamesTable) + local tiles = g_map.getTiles(posz()) + local targetTile = {amount=0,pos=false} + + for i, tile in pairs(tiles) do + local tPos = tile:getPosition() + local distance = distanceFromPlayer(tPos) + if tile:canShoot() and tile:isWalkable() and distance < 4 then + local amount = getMonstersInArea(2, tPos, pattern, minHp, maxHp, safePattern, monsterNamesTable) + if amount > targetTile.amount then + targetTile = {amount=amount,pos=tPos} + end + end + end + + return targetTile.amount > 0 and targetTile or false +end + +function executeAttackBotAction(categoryOrPos, idOrFormula, cooldown) + cooldown = cooldown or 0 + if categoryOrPos == 4 or categoryOrPos == 5 or categoryOrPos == 1 then + cast(idOrFormula, cooldown) + elseif categoryOrPos == 3 then + useWith(idOrFormula, target()) + end +end + +-- support function covered, now the main loop +macro(100, function() + if not currentSettings.enabled then return end + if #currentSettings.attackTable == 0 or isInPz() or not target() or modules.game_cooldown.isGroupCooldownIconActive(1) then return end + + if currentSettings.Training and target() and target():getName():lower():find("training") then return end + + if g_game.getClientVersion() < 960 or not currentSettings.Cooldown then + delay(400) + end + + local monstersN = 0 + local monstersE = 0 + local monstersS = 0 + local monstersW = 0 + monstersN = getCreaturesInArea(pos(), posN, 2) + monstersE = getCreaturesInArea(pos(), posE, 2) + monstersS = getCreaturesInArea(pos(), posS, 2) + monstersW = getCreaturesInArea(pos(), posW, 2) + local posTable = {monstersE, monstersN, monstersS, monstersW} + local bestSide = 0 + local bestDir + -- pulling out the biggest number + for i, v in pairs(posTable) do + if v > bestSide then + bestSide = v + end + end + -- associate biggest number with turn direction + if monstersN == bestSide then bestDir = 0 + elseif monstersE == bestSide then bestDir = 1 + elseif monstersS == bestSide then bestDir = 2 + elseif monstersW == bestSide then bestDir = 3 + end + + if currentSettings.Rotate then + if player:getDirection() ~= bestDir and bestSide > 0 then + turn(bestDir) + return + end + end + + -- support functions done, main spells now + --[[ + entry = { + creatures = creatures, + monsters = monsters, (formatted creatures) + mana = mana, + count = count, + minHp = minHp, + maxHp = maxHp, + cooldown = cooldown, + itemId = itemId, + spell = spell, + enabled = true, + category = category, + patternCategory = patternCategory, + pattern = pattern, + tooltip = tooltip, + description = '['..type..'] '..count.. 'x '..specificMonsters..': '..attackType..', '..categoryName..' ('..minHp..'%-'..maxHp..'%)' + } + ]] + + for i, child in ipairs(panel.entryList:getChildren()) do + local entry = child.params + local attackData = entry.itemId > 100 and entry.itemId or entry.spell + if entry.enabled and manapercent() >= entry.mana then + if (type(attackData) == "string" and canCast(entry.spell, not currentSettings.ignoreMana, not currentSettings.Cooldown)) or (entry.itemId > 100 and (not currentSettings.Visible or findItem(entry.itemId))) then + -- first PVP scenario + if currentSettings.pvpMode and target():getHealthPercent() >= entry.minHp and target():getHealthPercent() <= entry.maxHp and target():canShoot() then + if entry.category == 2 then + return warn("[AttackBot] Area Runes cannot be used in PVP situation!") + else + return executeAttackBotAction(entry.category, attackData, entry.cooldown) + end + end + -- empowerment + if entry.category == 4 and not isBuffed() then + local monsterAmount = getMonstersInArea(entry.category, nil, nil, entry.minHp, entry.maxHp, false, entry.monsters) + if (entry.orMore and monsterAmount >= entry.count or not entry.orMore and monsterAmount == entry.count) and distanceFromPlayer(target():getPosition()) <= entry.pattern then + return executeAttackBotAction(entry.category, attackData, entry.cooldown) + end + -- + elseif entry.category == 1 or entry.category == 3 then + local monsterAmount = getMonstersInArea(entry.category, nil, nil, entry.minHp, entry.maxHp, false, entry.monsters) + if (entry.orMore and monsterAmount >= entry.count or not entry.orMore and monsterAmount == entry.count) and distanceFromPlayer(target():getPosition()) <= entry.pattern then + return executeAttackBotAction(entry.category, attackData, entry.cooldown) + end + elseif entry.category == 5 then + local pCat = entry.patternCategory + local pattern = entry.pattern + local anchorParam = (pattern == 2 or pattern == 6 or pattern == 7 or pattern > 9) and player or pos() + local safe = currentSettings.PvpSafe and spellPatterns[pCat][entry.pattern][2] or false + local monsterAmount = pCat ~= 8 and getMonstersInArea(entry.category, anchorParam, spellPatterns[pCat][entry.pattern][1], entry.minHp, entry.maxHp, safe, entry.monsters) + if (pattern ~= 8 and (entry.orMore and monsterAmount >= entry.count or not entry.orMore and monsterAmount == entry.count)) or (pattern == 8 and bestSide >= entry.count and (not currentSettings.PvpSafe or getPlayers(2) == 0)) then + if (not currentSettings.BlackListSafe or not isBlackListedPlayerInRange(currentSettings.AntiRsRange)) and (not currentSettings.Kills or killsToRs() > currentSettings.KillsAmount) then + return executeAttackBotAction(entry.category, attackData, entry.cooldown) + end + end + elseif entry.category == 2 then + local pCat = entry.patternCategory + local safe = currentSettings.PvpSafe and spellPatterns[pCat][entry.pattern][2] or false + local data = getBestTileByPattern(spellPatterns[pCat][entry.pattern][1], entry.minHp, entry.maxHp, safe, entry.monsters) + local monsterAmount + local pos + if data then + monsterAmount = data.amount + pos = data.pos + end + if monsterAmount and (entry.orMore and monsterAmount >= entry.count or not entry.orMore and monsterAmount == entry.count) then + if (not currentSettings.BlackListSafe or not isBlackListedPlayerInRange(currentSettings.AntiRsRange)) and (not currentSettings.Kills or killsToRs() > currentSettings.KillsAmount) then + return useWith(attackData, g_map.getTile(pos):getTopUseThing()) + end + end + end + end + end + end +end) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/AttackBot.otui b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/AttackBot.otui new file mode 100644 index 0000000..f6329f4 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/AttackBot.otui @@ -0,0 +1,624 @@ +AttackEntry < UIWidget + background-color: alpha + text-offset: 35 1 + focusable: true + height: 16 + font: verdana-11px-rounded + text-align: left + + CheckBox + id: enabled + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + width: 15 + height: 15 + margin-top: 2 + margin-left: 3 + + UIItem + id: id + anchors.left: prev.right + anchors.verticalCenter: parent.verticalCenter + size: 16 16 + focusable: false + visible: false + + UIWidget + id: spell + anchors.left: enabled.right + anchors.verticalCenter: parent.verticalCenter + size: 12 12 + margin-left: 1 + image-source: /images/game/dangerous + + $focus: + background-color: #00000055 + + Button + id: remove + !text: tr('x') + anchors.right: parent.right + margin-right: 15 + width: 15 + height: 15 + +AttackBotBotPanel < Panel + height: 38 + + BotSwitch + id: title + anchors.top: parent.top + anchors.left: parent.left + text-align: center + width: 130 + !text: tr('AttackBot') + + Button + id: settings + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 3 + height: 17 + text: Setup + + Button + id: 1 + anchors.top: prev.bottom + anchors.left: parent.left + text: 1 + margin-right: 2 + margin-top: 4 + size: 17 17 + + Button + id: 2 + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + text: 2 + margin-left: 4 + size: 17 17 + + Button + id: 3 + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + text: 3 + margin-left: 4 + size: 17 17 + + Button + id: 4 + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + text: 4 + margin-left: 4 + size: 17 17 + + Button + id: 5 + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + text: 5 + margin-left: 4 + size: 17 17 + + Label + id: name + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + anchors.right: parent.right + text-align: center + margin-left: 4 + height: 17 + text: Profile #1 + background: #292A2A + +CategoryLabel < Panel + size: 315 15 + image-source: /images/ui/panel_flat + image-border: 5 + padding: 1 + + Label + id: description + anchors.fill: parent + text-align: center + text: Area Rune (avalanche, great fireball, etc) + font: verdana-11px-rounded + background: #363636 + +SourceLabel < Panel + size: 105 15 + image-source: /images/ui/panel_flat + image-border: 5 + padding: 1 + + Label + id: description + anchors.fill: parent + text-align: center + text: Monster Name + font: verdana-11px-rounded + background: #363636 + +RangeLabel < Panel + size: 323 15 + image-source: /images/ui/panel_flat + image-border: 5 + padding: 1 + + Label + id: description + anchors.fill: parent + text-align: center + text: 5 Sqm + font: verdana-11px-rounded + background: #363636 + +PreButton < PreviousButton + background: #363636 + height: 15 + +NexButton < NextButton + background: #363636 + height: 15 + +AttackBotPanel < Panel + size: 500 200 + image-source: /images/ui/panel_flat + image-border: 5 + padding: 5 + + TextList + id: entryList + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + margin-top: 3 + size: 430 100 + vertical-scrollbar: entryListScrollBar + + VerticalScrollBar + id: entryListScrollBar + anchors.top: entryList.top + anchors.bottom: entryList.bottom + anchors.right: entryList.right + step: 14 + pixels-scroll: true + + PreButton + id: previousCategory + anchors.left: entryList.left + anchors.top: entryList.bottom + margin-top: 8 + + NexButton + id: nextCategory + anchors.left: category.right + anchors.top: entryList.bottom + margin-top: 8 + margin-left: 2 + + CategoryLabel + id: category + anchors.top: entryList.bottom + anchors.left: previousCategory.right + anchors.verticalCenter: previousCategory.verticalCenter + margin-left: 3 + + PreButton + id: previousSource + anchors.left: entryList.left + anchors.top: category.bottom + margin-top: 8 + + NexButton + id: nextSource + anchors.left: source.right + anchors.top: category.bottom + margin-top: 8 + margin-left: 2 + + SourceLabel + id: source + anchors.top: category.bottom + anchors.left: previousSource.right + anchors.verticalCenter: previousSource.verticalCenter + margin-left: 3 + + PreButton + id: previousRange + anchors.left: nextSource.right + anchors.verticalCenter: nextSource.verticalCenter + margin-left: 8 + + NexButton + id: nextRange + anchors.left: range.right + anchors.verticalCenter: range.verticalCenter + margin-left: 2 + + RangeLabel + id: range + anchors.left: previousRange.right + anchors.verticalCenter: previousRange.verticalCenter + margin-left: 3 + + TextEdit + id: monsters + anchors.left: entryList.left + anchors.top: range.bottom + margin-top: 5 + size: 405 15 + text: monster names + font: cipsoftFont + background: #363636 + + Label + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 6 + margin-left: 3 + text-align: center + text: Mana%: + font: verdana-11px-rounded + + SpinBox + id: manaPercent + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 4 + size: 30 20 + minimum: 0 + maximum: 99 + step: 1 + editable: true + focusable: true + + Label + anchors.left: prev.right + margin-left: 7 + anchors.verticalCenter: prev.verticalCenter + text: Creatures: + font: verdana-11px-rounded + + SpinBox + id: creatures + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 4 + size: 30 20 + minimum: 1 + maximum: 99 + step: 1 + editable: true + focusable: true + + CheckBox + id: orMore + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 3 + tooltip: or more creatures + + Label + anchors.left: prev.right + margin-left: 7 + anchors.verticalCenter: prev.verticalCenter + text: HP: + font: verdana-11px-rounded + + SpinBox + id: minHp + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 4 + size: 40 20 + minimum: 0 + maximum: 99 + value: 0 + editable: true + focusable: true + + Label + anchors.left: prev.right + margin-left: 4 + anchors.verticalCenter: prev.verticalCenter + text: - + font: verdana-11px-rounded + + SpinBox + id: maxHp + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 4 + size: 40 20 + minimum: 1 + maximum: 100 + value: 100 + editable: true + focusable: true + + Label + anchors.left: prev.right + margin-left: 7 + anchors.verticalCenter: prev.verticalCenter + text: CD: + font: verdana-11px-rounded + + SpinBox + id: cooldown + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 4 + size: 60 20 + minimum: 0 + maximum: 999999 + step: 100 + value: 0 + editable: true + focusable: true + + Button + id: up + anchors.right: parent.right + anchors.top: entryList.bottom + size: 60 17 + text: Move Up + text-align: center + font: cipsoftFont + margin-top: 7 + margin-right: 8 + + Button + id: down + anchors.right: prev.left + anchors.verticalCenter: prev.verticalCenter + size: 60 17 + margin-right: 5 + text: Move Down + text-align: center + font: cipsoftFont + + Button + id: addEntry + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 40 19 + text-align: center + text: New + font: cipsoftFont + + BotItem + id: itemId + anchors.right: addEntry.left + margin-right: 5 + anchors.bottom: parent.bottom + margin-bottom: 2 + tooltip: drag item here on press to open window + + TextEdit + id: spellName + anchors.top: monsters.top + anchors.left: monsters.right + anchors.right: parent.right + margin-left: 5 + height: 15 + text: spell name + background: #363636 + font: cipsoftFont + visible: false + +SettingsPanel < Panel + size: 500 200 + image-source: /images/ui/panel_flat + image-border: 5 + padding: 10 + + VerticalSeparator + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: Visible.right + margin-left: 10 + margin-top: 5 + margin-bottom: 5 + + Label + anchors.top: parent.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 10 + text-align: center + font: verdana-11px-rounded + text: Profile: + + TextEdit + id: profileName + anchors.top: prev.bottom + margin-top: 3 + anchors.left: prev.left + anchors.right: prev.right + margin-left: 20 + margin-right: 20 + + Button + id: resetSettings + anchors.right: parent.right + anchors.bottom: parent.bottom + text-align: center + text: Reset Settings + + CheckBox + id: IgnoreMana + anchors.top: parent.top + anchors.left: parent.left + margin-top: 5 + width: 200 + text: Check RL Tibia conditions + + CheckBox + id: Kills + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 8 + width: 200 + height: 22 + text: Don't use area attacks if less than kills to red skull + text-wrap: true + text-align: left + + SpinBox + id: KillsAmount + anchors.top: prev.top + anchors.bottom: prev.bottom + anchors.left: prev.right + text-align: left + width: 30 + minimum: 1 + maximum: 10 + focusable: true + margin-left: 5 + + CheckBox + id: Rotate + anchors.top: Kills.bottom + anchors.left: Kills.left + margin-top: 8 + width: 220 + text: Turn to side with most monsters + + CheckBox + id: Cooldown + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 8 + width: 220 + text: Check spell cooldowns + + CheckBox + id: Visible + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 8 + width: 245 + text: Items must be visible (recommended) + + CheckBox + id: PvpMode + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 8 + width: 245 + text: PVP mode + + CheckBox + id: PvpSafe + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 8 + width: 245 + text: PVP safe + + CheckBox + id: Training + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 8 + width: 245 + text: Stop when attacking trainers + + CheckBox + id: BlackListSafe + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 8 + width: 200 + height: 18 + text: Stop if Anti-RS player in range + + SpinBox + id: AntiRsRange + anchors.top: prev.top + anchors.bottom: prev.bottom + anchors.left: prev.right + text-align: center + width: 50 + minimum: 1 + maximum: 10 + focusable: true + margin-left: 5 + +AttackBotWindow < MainWindow + size: 535 300 + padding: 15 + text: AttackBot v2 + @onEscape: self:hide() + + Label + id: mainLabel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + margin-top: 10 + margin-left: 2 + !text: tr('More important methods come first (Example: Exori gran above Exori)') + text-align: left + font: verdana-11px-rounded + color: #aeaeae + + SettingsPanel + id: settingsPanel + anchors.top: prev.bottom + margin-top: 10 + anchors.left: parent.left + margin-left: 2 + + Label + id: settingsLabel + anchors.verticalCenter: prev.top + anchors.left: prev.left + margin-left: 3 + text: Settings + color: #fe4400 + font: verdana-11px-rounded + + AttackBotPanel + id: mainPanel + anchors.top: mainLabel.bottom + margin-top: 10 + anchors.left: parent.left + margin-left: 2 + visible: false + + Label + id: shooterLabel + anchors.verticalCenter: prev.top + anchors.left: prev.left + margin-left: 3 + text: Spell Shooter + color: #fe4400 + font: verdana-11px-rounded + visible: false + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: closeButton.top + margin-bottom: 10 + + Button + id: closeButton + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 45 21 + text: Close + font: cipsoftFont + + Button + id: settings + anchors.left: parent.left + anchors.verticalCenter: prev.verticalCenter + size: 50 21 + font: cipsoftFont + text: Settings \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/BotServer.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/BotServer.lua new file mode 100644 index 0000000..1be5171 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/BotServer.lua @@ -0,0 +1,203 @@ +setDefaultTab("Main") + +local panelName = "BOTserver" +local ui = setupUI([[ +Panel + height: 18 + Button + id: botServer + anchors.left: parent.left + anchors.right: parent.right + text-align: center + height: 18 + !text: tr('BotServer') +]]) +ui:setId(panelName) + +if not storage[panelName] then + storage[panelName] = { + manaInfo = true, + mwallInfo = true, + vocation = true, + outfit = false, + broadcasts = true +} +end + +local config = storage[panelName] + +if not storage.BotServerChannel then + math.randomseed(os.time()) + storage.BotServerChannel = tostring(math.random(1000000000000,9999999999999)) +end + +local channel = tostring(storage.BotServerChannel) +BotServer.init(name(), channel) + +vBot.BotServerMembers = {} + +rootWidget = g_ui.getRootWidget() +if rootWidget then + botServerWindow = g_ui.createWidget('BotServerWindow', rootWidget) + botServerWindow:hide() + + + botServerWindow.Data.Channel:setText(storage.BotServerChannel) + botServerWindow.Data.Channel.onTextChange = function(widget, text) + storage.BotServerChannel = text + end + botServerWindow.Data.Random.onClick = function(widget) + storage.BotServerChannel = tostring(math.random(1000000000000,9999999999999)) + botServerWindow.Data.Channel:setText(storage.BotServerChannel) + end + botServerWindow.Features.Feature1:setOn(config.manaInfo) + botServerWindow.Features.Feature1.onClick = function(widget) + config.manaInfo = not config.manaInfo + widget:setOn(config.manaInfo) + end + botServerWindow.Features.Feature2:setOn(config.mwallInfo) + botServerWindow.Features.Feature2.onClick = function(widget) + config.mwallInfo = not config.mwallInfo + widget:setOn(config.mwallInfo) + end + botServerWindow.Features.Feature3:setOn(config.vocation) + botServerWindow.Features.Feature3.onClick = function(widget) + config.vocation = not config.vocation + if config.vocation then + BotServer.send("voc", player:getVocation()) + end + widget:setOn(config.vocation) + end + botServerWindow.Features.Feature4:setOn(config.outfit) + botServerWindow.Features.Feature4.onClick = function(widget) + config.outfit = not config.outfit + widget:setOn(config.outfit) + end + botServerWindow.Features.Feature5:setOn(config.broadcasts) + botServerWindow.Features.Feature5.onClick = function(widget) + config.broadcasts = not config.broadcasts + widget:setOn(config.broadcasts) + end + botServerWindow.Features.Broadcast.onClick = function(widget) + if BotServer._websocket then + BotServer.send("broadcast", botServerWindow.Features.broadcastText:getText()) + end + botServerWindow.Features.broadcastText:setText('') + end +end + +function updateStatusText() + if BotServer._websocket then + botServerWindow.Data.ServerStatus:setText("CONNECTED") + if serverCount then + botServerWindow.Data.Members:setText("Members: "..#serverCount) + if ServerMembers then + local text = "" + local regex = [["([a-z 'A-z-]*)"*]] + local re = regexMatch(ServerMembers, regex) + --re[name][2] + for i=1,#re do + if i == 1 then + text = re[i][2] + else + text = text .. "\n" .. re[i][2] + end + end + botServerWindow.Data.Members:setTooltip(text) + end + end + else + botServerWindow.Data.ServerStatus:setText("DISCONNECTED") + botServerWindow.Data.Participants:setText("-") + end +end + +macro(2000, function() + if BotServer._websocket then + BotServer.send("list") + end + updateStatusText() +end) + +local regex = [["(.*?)"]] +BotServer.listen("list", function(name, data) + serverCount = regexMatch(json.encode(data), regex) + ServerMembers = json.encode(data) +end) + +ui.botServer.onClick = function(widget) + botServerWindow:show() + botServerWindow:raise() + botServerWindow:focus() +end + +botServerWindow.closeButton.onClick = function(widget) + botServerWindow:hide() +end + +-- scripts + +-- mwalls +config.mwalls = {} +BotServer.listen("mwall", function(name, message) + if config.mwallInfo then + if not config.mwalls[message["pos"]] or config.mwalls[message["pos"]] < now then + config.mwalls[message["pos"]] = now + message["duration"] - 150 -- 150 is latency correction + end + end +end) + +onAddThing(function(tile, thing) + if config.mwallInfo then + if thing:isItem() and thing:getId() == 2129 then + local pos = tile:getPosition().x .. "," .. tile:getPosition().y .. "," .. tile:getPosition().z + if not config.mwalls[pos] or config.mwalls[pos] < now then + config.mwalls[pos] = now + 20000 + BotServer.send("mwall", {pos=pos, duration=20000}) + end + end + end +end) + +-- mana +local lastMana = 0 +macro(500, function() + if config.manaInfo then + if manapercent() ~= lastMana then + lastMana = manapercent() + BotServer.send("mana", {mana=lastMana}) + end + end +end) + +BotServer.listen("mana", function(name, message) + if config.manaInfo then + local creature = getPlayerByName(name) + if creature then + creature:setManaPercent(message["mana"]) + end + end +end) + +-- vocation +if config.vocation then + BotServer.send("voc", player:getVocation()) + BotServer.send("voc", "yes") +end + +BotServer.listen("voc", function(name, message) + if message == "yes" and config.vocation then + BotServer.send("voc", player:getVocation()) + else + vBot.BotServerMembers[name] = message + end +end) + +-- broadcast +BotServer.listen("broadcast", function(name, message) + if config.broadcasts then + broadcastMessage(name..": "..message) + end +end) + +addSeparator() \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/BotServer.otui b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/BotServer.otui new file mode 100644 index 0000000..ff6874b --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/BotServer.otui @@ -0,0 +1,188 @@ +BotServerData < Panel + size: 340 70 + image-source: /images/ui/window + image-border: 6 + padding: 3 + + Label + id: label + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-align: center + !text: tr("BotServer Data") + + Label + id: label + anchors.top: parent.top + anchors.left: parent.left + margin-top: 23 + text-align: center + text: Channel Name: + margin-left: 6 + + TextEdit + id: Channel + anchors.top: parent.top + anchors.left: prev.right + margin-top: 20 + width: 150 + margin-left: 5 + text-align: center + + Button + id: Random + anchors.left: prev.right + anchors.top: prev.top + anchors.right: parent.right + text-align: center + text: Randomize + margin-left: 6 + margin-right: 6 + + Label + id: label + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-left: 6 + margin-bottom: 4 + text-align: center + text: Status: + + BotLabel + id: ServerStatus + anchors.left: prev.right + anchors.bottom: parent.bottom + margin-left: 10 + margin-bottom: 4 + text-align: center + text: CONNECTED + + BotLabel + id: Participants + anchors.right: parent.right + anchors.bottom: parent.bottom + margin-right: 8 + margin-bottom: 4 + text-align: center + + UIWidget + id: Members + anchors.right: Participants.left + anchors.bottom: parent.bottom + size: 80 21 + text-align: center + text: Members: + +FeaturePanel < Panel + size: 340 150 + image-source: /images/ui/panel_flat + image-border: 5 + padding: 3 + + Label + id: title + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + text-align: center + text: Features + + HorizontalSeparator + id: sep + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 2 + + BotSwitch + id: Feature1 + anchors.top: prev.bottom + anchors.left: parent.left + margin-left: 3 + margin-top: 5 + text: Mana info + + BotSwitch + id: Feature2 + anchors.top: sep.bottom + anchors.left: prev.right + margin-top: 5 + margin-left: 5 + text: MWall info + + BotSwitch + id: Feature3 + anchors.top: sep.bottom + anchors.left: prev.right + margin-top: 5 + margin-left: 5 + text: Send Vocation + + BotSwitch + id: Feature4 + anchors.top: prev.bottom + anchors.left: parent.left + margin-top: 3 + margin-left: 3 + text: Outfit Vocation + + BotSwitch + id: Feature5 + anchors.bottom: prev.bottom + anchors.left: prev.right + margin-top: 3 + margin-left: 5 + text: Broadcasts + + + TextEdit + id: broadcastText + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-left: 3 + margin-bottom: 3 + margin-right: 80 + + Button + id: Broadcast + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-right: 3 + margin-left: 3 + height: 22 + text: Broadcast + +BotServerWindow < MainWindow + !text: tr('BotServer') + size: 370 310 + @onEscape: self:hide() + + BotServerData + id: Data + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + + FeaturePanel + id: Features + anchors.top: prev.bottom + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 10 + + HorizontalSeparator + id: separator + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: closeButton.top + margin-bottom: 8 + + Button + id: closeButton + !text: tr('Close') + font: cipsoftFont + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 45 21 + margin-top: 15 + margin-right: 5 \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/Conditions.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/Conditions.lua new file mode 100644 index 0000000..fa3c76d --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/Conditions.lua @@ -0,0 +1,262 @@ +setDefaultTab("HP") +local panelName = "ConditionPanel" +local ui = setupUI([[ +Panel + height: 19 + + BotSwitch + id: title + anchors.top: parent.top + anchors.left: parent.left + text-align: center + width: 130 + !text: tr('Conditions') + + Button + id: conditionList + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 3 + height: 17 + text: Setup + + ]]) + ui:setId(panelName) + + if not HealBotConfig[panelName] then + HealBotConfig[panelName] = { + enabled = false, + curePosion = false, + poisonCost = 20, + cureCurse = false, + curseCost = 80, + cureBleed = false, + bleedCost = 45, + cureBurn = false, + burnCost = 30, + cureElectrify = false, + electrifyCost = 22, + cureParalyse = false, + paralyseCost = 40, + paralyseSpell = "utani hur", + holdHaste = false, + hasteCost = 40, + hasteSpell = "utani hur", + holdUtamo = false, + utamoCost = 40, + holdUtana = false, + utanaCost = 440, + holdUtura = false, + uturaType = "", + uturaCost = 100, + ignoreInPz = true, + stopHaste = false + } + end + + local config = HealBotConfig[panelName] + + ui.title:setOn(config.enabled) + ui.title.onClick = function(widget) + config.enabled = not config.enabled + widget:setOn(config.enabled) + vBotConfigSave("heal") + end + + ui.conditionList.onClick = function(widget) + conditionsWindow:show() + conditionsWindow:raise() + conditionsWindow:focus() + end + + + + local rootWidget = g_ui.getRootWidget() + if rootWidget then + conditionsWindow = UI.createWindow('ConditionsWindow', rootWidget) + conditionsWindow:hide() + + + conditionsWindow.onVisibilityChange = function(widget, visible) + if not visible then + vBotConfigSave("heal") + end + end + + -- text edits + conditionsWindow.Cure.PoisonCost:setText(config.poisonCost) + conditionsWindow.Cure.PoisonCost.onTextChange = function(widget, text) + config.poisonCost = tonumber(text) + end + + conditionsWindow.Cure.CurseCost:setText(config.curseCost) + conditionsWindow.Cure.CurseCost.onTextChange = function(widget, text) + config.curseCost = tonumber(text) + end + + conditionsWindow.Cure.BleedCost:setText(config.bleedCost) + conditionsWindow.Cure.BleedCost.onTextChange = function(widget, text) + config.bleedCost = tonumber(text) + end + + conditionsWindow.Cure.BurnCost:setText(config.burnCost) + conditionsWindow.Cure.BurnCost.onTextChange = function(widget, text) + config.burnCost = tonumber(text) + end + + conditionsWindow.Cure.ElectrifyCost:setText(config.electrifyCost) + conditionsWindow.Cure.ElectrifyCost.onTextChange = function(widget, text) + config.electrifyCost = tonumber(text) + end + + conditionsWindow.Cure.ParalyseCost:setText(config.paralyseCost) + conditionsWindow.Cure.ParalyseCost.onTextChange = function(widget, text) + config.paralyseCost = tonumber(text) + end + + conditionsWindow.Cure.ParalyseSpell:setText(config.paralyseSpell) + conditionsWindow.Cure.ParalyseSpell.onTextChange = function(widget, text) + config.paralyseSpell = text + end + + conditionsWindow.Hold.HasteSpell:setText(config.hasteSpell) + conditionsWindow.Hold.HasteSpell.onTextChange = function(widget, text) + config.hasteSpell = text + end + + conditionsWindow.Hold.HasteCost:setText(config.hasteCost) + conditionsWindow.Hold.HasteCost.onTextChange = function(widget, text) + config.hasteCost = tonumber(text) + end + + conditionsWindow.Hold.UtamoCost:setText(config.utamoCost) + conditionsWindow.Hold.UtamoCost.onTextChange = function(widget, text) + config.utamoCost = tonumber(text) + end + + conditionsWindow.Hold.UtanaCost:setText(config.utanaCost) + conditionsWindow.Hold.UtanaCost.onTextChange = function(widget, text) + config.utanaCost = tonumber(text) + end + + conditionsWindow.Hold.UturaCost:setText(config.uturaCost) + conditionsWindow.Hold.UturaCost.onTextChange = function(widget, text) + config.uturaCost = tonumber(text) + end + + -- combo box + conditionsWindow.Hold.UturaType:setOption(config.uturaType) + conditionsWindow.Hold.UturaType.onOptionChange = function(widget) + config.uturaType = widget:getCurrentOption().text + end + + -- checkboxes + conditionsWindow.Cure.CurePoison:setChecked(config.curePoison) + conditionsWindow.Cure.CurePoison.onClick = function(widget) + config.curePoison = not config.curePoison + widget:setChecked(config.curePoison) + end + + conditionsWindow.Cure.CureCurse:setChecked(config.cureCurse) + conditionsWindow.Cure.CureCurse.onClick = function(widget) + config.cureCurse = not config.cureCurse + widget:setChecked(config.cureCurse) + end + + conditionsWindow.Cure.CureBleed:setChecked(config.cureBleed) + conditionsWindow.Cure.CureBleed.onClick = function(widget) + config.cureBleed = not config.cureBleed + widget:setChecked(config.cureBleed) + end + + conditionsWindow.Cure.CureBurn:setChecked(config.cureBurn) + conditionsWindow.Cure.CureBurn.onClick = function(widget) + config.cureBurn = not config.cureBurn + widget:setChecked(config.cureBurn) + end + + conditionsWindow.Cure.CureElectrify:setChecked(config.cureElectrify) + conditionsWindow.Cure.CureElectrify.onClick = function(widget) + config.cureElectrify = not config.cureElectrify + widget:setChecked(config.cureElectrify) + end + + conditionsWindow.Cure.CureParalyse:setChecked(config.cureParalyse) + conditionsWindow.Cure.CureParalyse.onClick = function(widget) + config.cureParalyse = not config.cureParalyse + widget:setChecked(config.cureParalyse) + end + + conditionsWindow.Hold.HoldHaste:setChecked(config.holdHaste) + conditionsWindow.Hold.HoldHaste.onClick = function(widget) + config.holdHaste = not config.holdHaste + widget:setChecked(config.holdHaste) + end + + conditionsWindow.Hold.HoldUtamo:setChecked(config.holdUtamo) + conditionsWindow.Hold.HoldUtamo.onClick = function(widget) + config.holdUtamo = not config.holdUtamo + widget:setChecked(config.holdUtamo) + end + + conditionsWindow.Hold.HoldUtana:setChecked(config.holdUtana) + conditionsWindow.Hold.HoldUtana.onClick = function(widget) + config.holdUtana = not config.holdUtana + widget:setChecked(config.holdUtana) + end + + conditionsWindow.Hold.HoldUtura:setChecked(config.holdUtura) + conditionsWindow.Hold.HoldUtura.onClick = function(widget) + config.holdUtura = not config.holdUtura + widget:setChecked(config.holdUtura) + end + + conditionsWindow.Hold.IgnoreInPz:setChecked(config.ignoreInPz) + conditionsWindow.Hold.IgnoreInPz.onClick = function(widget) + config.ignoreInPz = not config.ignoreInPz + widget:setChecked(config.ignoreInPz) + end + + conditionsWindow.Hold.StopHaste:setChecked(config.stopHaste) + conditionsWindow.Hold.StopHaste.onClick = function(widget) + config.stopHaste = not config.stopHaste + widget:setChecked(config.stopHaste) + end + + -- buttons + conditionsWindow.closeButton.onClick = function(widget) + conditionsWindow:hide() + end + + Conditions = {} + Conditions.show = function() + conditionsWindow:show() + conditionsWindow:raise() + conditionsWindow:focus() + end + end + + local utanaCast = nil + macro(500, function() + if not config.enabled or modules.game_cooldown.isGroupCooldownIconActive(2) then return end + if hppercent() > 95 then + if config.curePoison and mana() >= config.poisonCost and isPoisioned() then say("exana pox") + elseif config.cureCurse and mana() >= config.curseCost and isCursed() then say("exana mort") + elseif config.cureBleed and mana() >= config.bleedCost and isBleeding() then say("exana kor") + elseif config.cureBurn and mana() >= config.burnCost and isBurning() then say("exana flam") + elseif config.cureElectrify and mana() >= config.electrifyCost and isEnergized() then say("exana vis") + end + end + if (not config.ignoreInPz or not isInPz()) and config.holdUtura and mana() >= config.uturaCost and canCast(config.uturaType) and hppercent() < 90 then say(config.uturaType) + elseif (not config.ignoreInPz or not isInPz()) and config.holdUtana and mana() >= config.utanaCost and (not utanaCast or (now - utanaCast > 120000)) then say("utana vid") utanaCast = now + end + end) + + macro(50, function() + if not config.enabled then return end + if (not config.ignoreInPz or not isInPz()) and config.holdUtamo and mana() >= config.utamoCost and not hasManaShield() then say("utamo vita") + elseif (not config.ignoreInPz or not isInPz()) and standTime() < 5000 and config.holdHaste and mana() >= config.hasteCost and not hasHaste() and not getSpellCoolDown(config.hasteSpell) and (not target() or not config.stopHaste or TargetBot.isCaveBotActionAllowed()) then say(config.hasteSpell) + elseif config.cureParalyse and mana() >= config.paralyseCost and isParalyzed() and not getSpellCoolDown(config.paralyseSpell) then say(config.paralyseSpell) + end + end) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/Conditions.otui b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/Conditions.otui new file mode 100644 index 0000000..ee8d43b --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/Conditions.otui @@ -0,0 +1,463 @@ +UturaComboBoxPopupMenu < ComboBoxPopupMenu +UturaComboBoxPopupMenuButton < ComboBoxPopupMenuButton +UturaComboBox < ComboBox + @onSetup: | + self:addOption("Utura") + self:addOption("Utura Gran") + +CureConditions < Panel + id: Cure + image-source: /images/ui/panel_flat + image-border: 6 + padding: 3 + size: 200 190 + + Label + id: label1 + anchors.top: parent.top + anchors.left: parent.left + margin-top: 10 + margin-left: 5 + text: Poison + color: #ffaa00 + font: verdana-11px-rounded + + Label + id: label11 + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 40 + text: Mana: + font: verdana-11px-rounded + + TextEdit + id: PoisonCost + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 3 + width: 40 + font: verdana-11px-rounded + + CheckBox + id: CurePoison + anchors.verticalCenter: prev.verticalCenter + anchors.right: parent.right + margin-right: 10 + + Label + id: label2 + anchors.left: label1.left + anchors.top: label1.bottom + margin-top: 10 + text: Curse + color: #ffaa00 + font: verdana-11px-rounded + + Label + id: label22 + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 44 + text: Mana: + font: verdana-11px-rounded + + TextEdit + id: CurseCost + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 3 + width: 40 + font: verdana-11px-rounded + + CheckBox + id: CureCurse + anchors.verticalCenter: prev.verticalCenter + anchors.right: parent.right + margin-right: 10 + + Label + id: label3 + anchors.left: label2.left + anchors.top: label2.bottom + margin-top: 10 + text: Bleed + color: #ffaa00 + font: verdana-11px-rounded + + Label + id: label33 + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 46 + text: Mana: + font: verdana-11px-rounded + + TextEdit + id: BleedCost + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 3 + width: 40 + font: verdana-11px-rounded + + CheckBox + id: CureBleed + anchors.verticalCenter: prev.verticalCenter + anchors.right: parent.right + margin-right: 10 + + Label + id: label4 + anchors.left: label3.left + anchors.top: label3.bottom + margin-top: 10 + text: Burn + color: #ffaa00 + font: verdana-11px-rounded + + Label + id: label44 + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 50 + text: Mana: + font: verdana-11px-rounded + + TextEdit + id: BurnCost + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 3 + width: 40 + font: verdana-11px-rounded + + CheckBox + id: CureBurn + anchors.verticalCenter: prev.verticalCenter + anchors.right: parent.right + margin-right: 10 + + Label + id: label5 + anchors.left: label4.left + anchors.top: label4.bottom + margin-top: 10 + text: Electify + color: #ffaa00 + font: verdana-11px-rounded + + Label + id: label55 + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 33 + text: Mana: + font: verdana-11px-rounded + + TextEdit + id: ElectrifyCost + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 3 + width: 40 + font: verdana-11px-rounded + + CheckBox + id: CureElectrify + anchors.verticalCenter: prev.verticalCenter + anchors.right: parent.right + margin-right: 10 + + Label + id: label6 + anchors.left: label5.left + anchors.top: label5.bottom + margin-top: 10 + text: Paralyse + color: #ffaa00 + font: verdana-11px-rounded + + Label + id: label66 + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 26 + text: Mana: + font: verdana-11px-rounded + + TextEdit + id: ParalyseCost + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 3 + width: 40 + font: verdana-11px-rounded + + CheckBox + id: CureParalyse + anchors.verticalCenter: prev.verticalCenter + anchors.right: parent.right + margin-right: 10 + + Label + id: label7 + anchors.left: label6.left + anchors.top: label6.bottom + margin-top: 10 + margin-left: 12 + text: Spell: + font: verdana-11px-rounded + + TextEdit + id: ParalyseSpell + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 10 + width: 100 + font: verdana-11px-rounded + +HoldConditions < Panel + id: Hold + image-source: /images/ui/panel_flat + image-border: 6 + padding: 3 + size: 200 190 + + Label + id: label1 + anchors.top: parent.top + anchors.left: parent.left + margin-top: 10 + margin-left: 5 + text: Haste + color: #ffaa00 + font: verdana-11px-rounded + + Label + id: label11 + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 44 + text: Mana: + font: verdana-11px-rounded + + TextEdit + id: HasteCost + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 3 + width: 40 + font: verdana-11px-rounded + + CheckBox + id: HoldHaste + anchors.verticalCenter: prev.verticalCenter + anchors.right: parent.right + margin-right: 10 + + Label + id: label2 + anchors.left: label1.left + anchors.top: label1.bottom + margin-top: 10 + margin-left: 12 + text: Spell: + font: verdana-11px-rounded + + TextEdit + id: HasteSpell + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 10 + width: 100 + font: verdana-11px-rounded + + Label + id: label3 + anchors.left: label1.left + anchors.top: label2.bottom + margin-top: 10 + text: Utana Vid + color: #ffaa00 + font: verdana-11px-rounded + + Label + id: label33 + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 21 + text: Mana: + font: verdana-11px-rounded + + TextEdit + id: UtanaCost + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 3 + width: 40 + font: verdana-11px-rounded + + CheckBox + id: HoldUtana + anchors.verticalCenter: prev.verticalCenter + anchors.right: parent.right + margin-right: 10 + + Label + id: label4 + anchors.left: label3.left + anchors.top: label3.bottom + margin-top: 10 + text: Utamo Vita + color: #ffaa00 + font: verdana-11px-rounded + + Label + id: label44 + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 12 + text: Mana: + font: verdana-11px-rounded + + TextEdit + id: UtamoCost + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 3 + width: 40 + font: verdana-11px-rounded + + CheckBox + id: HoldUtamo + anchors.verticalCenter: prev.verticalCenter + anchors.right: parent.right + margin-right: 10 + + Label + id: label5 + anchors.left: label4.left + anchors.top: label4.bottom + margin-top: 10 + text: Recovery + color: #ffaa00 + font: verdana-11px-rounded + + Label + id: label55 + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 20 + text: Mana: + font: verdana-11px-rounded + + TextEdit + id: UturaCost + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 3 + width: 40 + font: verdana-11px-rounded + + CheckBox + id: HoldUtura + anchors.verticalCenter: prev.verticalCenter + anchors.right: parent.right + margin-right: 10 + + Label + id: label6 + anchors.left: label5.left + anchors.top: label5.bottom + margin-top: 10 + margin-left: 12 + text: Spell: + font: verdana-11px-rounded + + UturaComboBox + id: UturaType + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 10 + width: 100 + font: verdana-11px-rounded + + CheckBox + id: IgnoreInPz + anchors.left: label5.left + anchors.top: label6.bottom + margin-top: 12 + + Label + anchors.verticalCenter: IgnoreInPz.verticalCenter + anchors.left: prev.right + margin-top: 3 + margin-left: 5 + text: Don't Cast in Protection Zones + font: cipsoftFont + + CheckBox + id: StopHaste + anchors.horizontalCenter: IgnoreInPz.horizontalCenter + anchors.top: IgnoreInPz.bottom + margin-top: 8 + + Label + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-top: 3 + margin-left: 5 + text: Stop Haste if TargetBot Is Active + font: cipsoftFont + +ConditionsWindow < MainWindow + !text: tr('Condition Manager') + size: 445 280 + @onEscape: self:hide() + + CureConditions + id: Cure + anchors.top: parent.top + anchors.left: parent.left + margin-top: 7 + + Label + id: label + anchors.top: parent.top + anchors.left: parent.left + text: Cure Conditions + color: #88e3dd + margin-left: 10 + font: verdana-11px-rounded + + HoldConditions + id: Hold + anchors.top: parent.top + anchors.right: parent.right + margin-top: 7 + + Label + id: label + anchors.top: parent.top + anchors.right: parent.right + text: Hold Conditions + color: #88e3dd + margin-right: 100 + font: verdana-11px-rounded + + HorizontalSeparator + id: separator + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: closeButton.top + margin-bottom: 8 + + Button + id: closeButton + !text: tr('Close') + font: cipsoftFont + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 45 21 + margin-top: 15 + margin-right: 5 \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/Containers.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/Containers.lua new file mode 100644 index 0000000..ca0f9ce --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/Containers.lua @@ -0,0 +1,626 @@ +setDefaultTab("Tools") +local panelName = "renameContainers" +if type(storage[panelName]) ~= "table" then + storage[panelName] = { + enabled = false; + height = 170, + purse = true; + list = { + { + value = "Main Backpack", + enabled = true, + item = 9601, + min = false, + items = { 3081, 3048 } + }, + { + value = "Runes", + enabled = true, + item = 2866, + min = true, + items = { 3161, 3180 } + }, + { + value = "Money", + enabled = true, + item = 2871, + min = true, + items = { 3031, 3035, 3043 } + }, + { + value = "Purse", + enabled = true, + item = 23396, + min = true, + items = {} + }, + } + } +end + +local config = storage[panelName] + +local renameContui = setupUI([[ +Panel + height: 38 + + BotSwitch + id: title + anchors.top: parent.top + anchors.left: parent.left + text-align: center + width: 130 + !text: tr('Minimise Containers') + + Button + id: editContList + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 3 + height: 17 + text: Setup + + Button + id: reopenCont + !text: tr('Reopen Containers') + anchors.left: parent.left + anchors.top: prev.bottom + anchors.right: parent.right + height: 17 + margin-top: 3 + + ]]) +renameContui:setId(panelName) + +g_ui.loadUIFromString([[ +BackpackName < Label + background-color: alpha + text-offset: 18 2 + focusable: true + height: 17 + font: verdana-11px-rounded + + CheckBox + id: enabled + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + width: 15 + height: 15 + margin-top: 1 + margin-left: 3 + + $focus: + background-color: #00000055 + + Button + id: state + !text: tr('M') + anchors.right: remove.left + anchors.verticalCenter: parent.verticalCenter + margin-right: 1 + width: 15 + height: 15 + + Button + id: remove + !text: tr('X') + !tooltip: tr('Remove') + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + margin-right: 15 + width: 15 + height: 15 + + Button + id: openNext + !text: tr('N') + anchors.right: state.left + anchors.verticalCenter: parent.verticalCenter + margin-right: 1 + width: 15 + height: 15 + tooltip: Open container inside with the same ID. + +ContListsWindow < MainWindow + !text: tr('Container Names') + size: 465 170 + @onEscape: self:hide() + + TextList + id: itemList + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: separator.top + width: 200 + margin-bottom: 6 + margin-top: 3 + margin-left: 3 + vertical-scrollbar: itemListScrollBar + + VerticalScrollBar + id: itemListScrollBar + anchors.top: itemList.top + anchors.bottom: itemList.bottom + anchors.right: itemList.right + step: 14 + pixels-scroll: true + + VerticalSeparator + id: sep + anchors.top: parent.top + anchors.left: itemList.right + anchors.bottom: separator.top + margin-top: 3 + margin-bottom: 6 + margin-left: 10 + + Label + id: lblName + anchors.left: sep.right + anchors.top: sep.top + width: 70 + text: Name: + margin-left: 10 + margin-top: 3 + font: verdana-11px-rounded + + TextEdit + id: contName + anchors.left: lblName.right + anchors.top: sep.top + anchors.right: parent.right + font: verdana-11px-rounded + + Label + id: lblCont + anchors.left: lblName.left + anchors.verticalCenter: contId.verticalCenter + width: 70 + text: Container: + font: verdana-11px-rounded + + BotItem + id: contId + anchors.left: contName.left + anchors.top: contName.bottom + margin-top: 3 + + BotContainer + id: sortList + anchors.left: prev.left + anchors.right: parent.right + anchors.top: prev.bottom + anchors.bottom: separator.top + margin-bottom: 6 + margin-top: 3 + + Label + anchors.left: lblCont.left + anchors.verticalCenter: sortList.verticalCenter + width: 70 + text: Items: + font: verdana-11px-rounded + + Button + id: addItem + anchors.right: contName.right + anchors.top: contName.bottom + margin-top: 5 + text: Add + width: 40 + font: cipsoftFont + + HorizontalSeparator + id: separator + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: closeButton.top + margin-bottom: 8 + + CheckBox + id: purse + anchors.left: parent.left + anchors.bottom: parent.bottom + text: Open Purse + tooltip: Opens Store/Charm Purse + width: 85 + height: 15 + margin-top: 2 + margin-left: 3 + font: verdana-11px-rounded + + CheckBox + id: sort + anchors.left: prev.right + anchors.bottom: parent.bottom + text: Sort Items + tooltip: Sort items based on items widget + width: 85 + height: 15 + margin-top: 2 + margin-left: 15 + font: verdana-11px-rounded + + CheckBox + id: forceOpen + anchors.left: prev.right + anchors.bottom: parent.bottom + text: Keep Open + tooltip: Will keep open containers all the time + width: 85 + height: 15 + margin-top: 2 + margin-left: 15 + font: verdana-11px-rounded + + CheckBox + id: lootBag + anchors.left: prev.right + anchors.bottom: parent.bottom + text: Loot Bag + tooltip: Open Loot Bag (gunzodus franchaise) + width: 85 + height: 15 + margin-top: 2 + margin-left: 15 + font: verdana-11px-rounded + + Button + id: closeButton + !text: tr('Close') + font: cipsoftFont + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 45 21 + margin-top: 15 + + ResizeBorder + id: bottomResizeBorder + anchors.fill: separator + height: 3 + minimum: 170 + maximum: 245 + margin-left: 3 + margin-right: 3 + background: #ffffff88 +]]) + +function findItemsInArray(t, tfind) + local tArray = {} + for x,v in pairs(t) do + if type(v) == "table" then + local aItem = t[x].item + local aEnabled = t[x].enabled + if aItem then + if tfind and aItem == tfind then + return x + elseif not tfind then + if aEnabled then + table.insert(tArray, aItem) + end + end + end + end + end + if not tfind then return tArray end +end + +local lstBPs + + +local openContainer = function(id) + local t = {getRight(), getLeft(), getAmmo()} -- if more slots needed then add them here + for i=1,#t do + local slotItem = t[i] + if slotItem and slotItem:getId() == id then + return g_game.open(slotItem, nil) + end + end + + for i, container in pairs(g_game.getContainers()) do + for i, item in ipairs(container:getItems()) do + if item:isContainer() and item:getId() == id then + return g_game.open(item, nil) + end + end + end +end + +function reopenBackpacks() + lstBPs = findItemsInArray(config.list) + + for _, container in pairs(g_game.getContainers()) do g_game.close(container) end + bpItem = getBack() + if bpItem ~= nil then + g_game.open(bpItem) + end + + schedule(500, function() + local delay = 200 + + if config.purse then + local item = getPurse() + if item then + use(item) + end + end + for i=1,#lstBPs do + schedule(delay, function() + openContainer(lstBPs[i]) + end) + delay = delay + 250 + end + end) + +end + +rootWidget = g_ui.getRootWidget() +if rootWidget then + contListWindow = UI.createWindow('ContListsWindow', rootWidget) + contListWindow:hide() + + contListWindow.onGeometryChange = function(widget, old, new) + if old.height == 0 then return end + + config.height = new.height + end + + contListWindow:setHeight(config.height or 170) + + renameContui.editContList.onClick = function(widget) + contListWindow:show() + contListWindow:raise() + contListWindow:focus() + end + + renameContui.reopenCont.onClick = function(widget) + reopenBackpacks() + end + + renameContui.title:setOn(config.enabled) + renameContui.title.onClick = function(widget) + config.enabled = not config.enabled + widget:setOn(config.enabled) + end + + contListWindow.closeButton.onClick = function(widget) + contListWindow:hide() + end + + contListWindow.purse.onClick = function(widget) + config.purse = not config.purse + contListWindow.purse:setChecked(config.purse) + end + contListWindow.purse:setChecked(config.purse) + + contListWindow.sort.onClick = function(widget) + config.sort = not config.sort + contListWindow.sort:setChecked(config.sort) + end + contListWindow.sort:setChecked(config.sort) + + contListWindow.forceOpen.onClick = function(widget) + config.forceOpen = not config.forceOpen + contListWindow.forceOpen:setChecked(config.forceOpen) + end + contListWindow.forceOpen:setChecked(config.forceOpen) + + contListWindow.lootBag.onClick = function(widget) + config.lootBag = not config.lootBag + contListWindow.lootBag:setChecked(config.lootBag) + end + contListWindow.lootBag:setChecked(config.lootBag) + + local function refreshSortList(k, t) + t = t or {} + UI.Container(function() + t = contListWindow.sortList:getItems() + config.list[k].items = t + end, true, nil, contListWindow.sortList) + contListWindow.sortList:setItems(t) + end + refreshSortList(t) + + local refreshContNames = function(tFocus) + local storageVal = config.list + if storageVal and #storageVal > 0 then + for i, child in pairs(contListWindow.itemList:getChildren()) do + child:destroy() + end + for k, entry in pairs(storageVal) do + local label = g_ui.createWidget("BackpackName", contListWindow.itemList) + label.onMouseRelease = function() + contListWindow.contId:setItemId(entry.item) + contListWindow.contName:setText(entry.value) + if not entry.items then + entry.items = {} + end + contListWindow.sortList:setItems(entry.items) + refreshSortList(k, entry.items) + end + label.enabled.onClick = function(widget) + entry.enabled = not entry.enabled + label.enabled:setChecked(entry.enabled) + label.enabled:setTooltip(entry.enabled and 'Disable' or 'Enable') + label.enabled:setImageColor(entry.enabled and '#00FF00' or '#FF0000') + end + label.remove.onClick = function(widget) + table.removevalue(config.list, entry) + label:destroy() + end + label.state:setChecked(entry.min) + label.state.onClick = function(widget) + entry.min = not entry.min + label.state:setChecked(entry.min) + label.state:setColor(entry.min and '#00FF00' or '#FF0000') + label.state:setTooltip(entry.min and 'Open Minimised' or 'Do not minimise') + end + label.openNext.onClick = function(widget) + entry.openNext = not entry.openNext + label.openNext:setChecked(entry.openNext) + label.openNext:setColor(entry.openNext and '#00FF00' or '#FF0000') + end + label:setText(entry.value) + label.enabled:setChecked(entry.enabled) + label.enabled:setTooltip(entry.enabled and 'Disable' or 'Enable') + label.enabled:setImageColor(entry.enabled and '#00FF00' or '#FF0000') + label.state:setColor(entry.min and '#00FF00' or '#FF0000') + label.state:setTooltip(entry.min and 'Open Minimised' or 'Do not minimise') + label.openNext:setColor(entry.openNext and '#00FF00' or '#FF0000') + + if tFocus and entry.item == tFocus then + tFocus = label + end + end + if tFocus then contListWindow.itemList:focusChild(tFocus) end + end + end + contListWindow.addItem.onClick = function(widget) + local id = contListWindow.contId:getItemId() + local trigger = contListWindow.contName:getText() + + if id > 100 and trigger:len() > 0 then + local ifind = findItemsInArray(config.list, id) + if ifind then + config.list[ifind] = { item = id, value = trigger, enabled = config.list[ifind].enabled, min = config.list[ifind].min, items = config.list[ifind].items} + else + table.insert(config.list, { item = id, value = trigger, enabled = true, min = false, items = {} }) + end + contListWindow.contId:setItemId(0) + contListWindow.contName:setText('') + contListWindow.contName:setColor('white') + contListWindow.contName:setImageColor('#ffffff') + contListWindow.contId:setImageColor('#ffffff') + refreshContNames(id) + else + contListWindow.contId:setImageColor('red') + contListWindow.contName:setImageColor('red') + contListWindow.contName:setColor('red') + end + end + refreshContNames() +end + +onContainerOpen(function(container, previousContainer) + if not container.window then return end + local containerWindow = container.window + if not previousContainer then + containerWindow:setContentHeight(34) + end + + local storageVal = config.list + if storageVal and #storageVal > 0 then + for _, entry in pairs(storageVal) do + if entry.enabled and string.find(container:getContainerItem():getId(), entry.item) then + if entry.min then + containerWindow:minimize() + end + if renameContui.title:isOn() then + containerWindow:setText(entry.value) + end + if entry.openNext then + for i, item in ipairs(container:getItems()) do + if item:getId() == entry.item then + local time = #storageVal * 250 + schedule(time, function() + time = time + 250 + g_game.open(item) + end) + end + end + end + end + end + end +end) + +local function nameContainersOnLogin() + for i, container in ipairs(getContainers()) do + if renameContui.title:isOn() then + if not container.window then return end + local containerWindow = container.window + local storageVal = config.list + if storageVal and #storageVal > 0 then + for _, entry in pairs(storageVal) do + if entry.enabled and string.find(container:getContainerItem():getId(), entry.item) then + containerWindow:setText(entry.value) + end + end + end + end + end +end +nameContainersOnLogin() + +local function moveItem(item, destination) + return g_game.move(item, destination:getSlotPosition(destination:getItemsCount()), item:getCount()) +end + +local function properTable(t) + local r = {} + for _, entry in pairs(t) do + if type(entry) == "number" then + table.insert(r, entry) + else + table.insert(r, entry.id) + end + end + return r +end + +macro(500, function() + if not config.sort and not config.purse then return end + + local storageVal = config.list + for _, entry in pairs(storageVal) do + local dId = entry.item + local items = properTable(entry.items) + -- sorting + if config.sort then + for _, container in pairs(getContainers()) do + local cName = container:getName():lower() + if not cName:find("depot") and not cName:find("depot") and not cName:find("quiver") then + local cId = container:getContainerItem():getId() + for __, item in ipairs(container:getItems()) do + local id = item:getId() + if table.find(items, id) and cId ~= dId then + local destination = getContainerByItem(dId, true) + if destination and not containerIsFull(destination) then + return moveItem(item, destination) + end + end + end + end + end + end + -- keep open / purse 23396 + if config.forceOpen then + local container = getContainerByItem(dId) + if not container then + local t = {getBack(), getAmmo(), getFinger(), getNeck(), getLeft(), getRight()} + for i=1,#t do + local slot = t[i] + if slot and slot:getId() == dId then + return g_game.open(slot) + end + end + local cItem = findItem(dId) + if cItem then + return g_game.open(cItem) + end + end + end + end + if config.purse and config.forceOpen and not getContainerByItem(23396) then + return use(getPurse()) + end + if config.lootBag and config.forceOpen and not getContainerByItem(23721) then + if findItem(23721) then + g_game.open(findItem(23721), getContainerByItem(23396)) + else + return use(getPurse()) + end + end + delay(1500) +end) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/Dropper.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/Dropper.lua new file mode 100644 index 0000000..96674b9 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/Dropper.lua @@ -0,0 +1,146 @@ +setDefaultTab("Tools") + +local ui = setupUI([[ +Panel + height: 19 + + BotSwitch + id: title + anchors.top: parent.top + anchors.left: parent.left + text-align: center + width: 130 + !text: tr('Dropper') + + Button + id: edit + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 3 + height: 17 + text: Edit +]]) + +local edit = setupUI([[ +Panel + height: 150 + + Label + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + margin-top: 5 + text-align: center + text: Trash: + + BotContainer + id: TrashItems + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 32 + + Label + anchors.top: prev.bottom + margin-top: 5 + anchors.left: parent.left + anchors.right: parent.right + text-align: center + text: Use: + + BotContainer + id: UseItems + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 32 + + Label + anchors.top: prev.bottom + margin-top: 5 + anchors.left: parent.left + anchors.right: parent.right + text-align: center + text: Drop if below 150 cap: + + BotContainer + id: CapItems + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 32 +]]) +edit:hide() + +if not storage.dropper then + storage.dropper = { + enabled = false, + trashItems = { 283, 284, 285 }, + useItems = { 21203, 14758 }, + capItems = { 21175 } + } +end + +local config = storage.dropper + +local showEdit = false +ui.edit.onClick = function(widget) + showEdit = not showEdit + if showEdit then + edit:show() + else + edit:hide() + end +end + +ui.title:setOn(config.enabled) +ui.title.onClick = function(widget) + config.enabled = not config.enabled + ui.title:setOn(config.enabled) +end + +UI.Container(function() + config.trashItems = edit.TrashItems:getItems() + end, true, nil, edit.TrashItems) +edit.TrashItems:setItems(config.trashItems) + +UI.Container(function() + config.useItems = edit.UseItems:getItems() + end, true, nil, edit.UseItems) +edit.UseItems:setItems(config.useItems) + +UI.Container(function() + config.capItems = edit.CapItems:getItems() + end, true, nil, edit.CapItems) +edit.CapItems:setItems(config.capItems) + +local function properTable(t) + local r = {} + + for _, entry in pairs(t) do + table.insert(r, entry.id) + end + return r +end + +macro(200, function() + if not config.enabled then return end + local tables = {properTable(config.capItems), properTable(config.useItems), properTable(config.trashItems)} + + local containers = getContainers() + for i=1,3 do + for _, container in pairs(containers) do + for __, item in ipairs(container:getItems()) do + for ___, userItem in ipairs(tables[i]) do + if item:getId() == userItem then + return i == 1 and freecap() < 150 and dropItem(item) or + i == 2 and use(item) or + i == 3 and dropItem(item) + end + end + end + end + end + +end) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/Equipper.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/Equipper.lua new file mode 100644 index 0000000..a9442bc --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/Equipper.lua @@ -0,0 +1,679 @@ +local panelName = "EquipperPanel" +local ui = setupUI([[ +Panel + height: 19 + + BotSwitch + id: switch + anchors.top: parent.top + anchors.left: parent.left + text-align: center + width: 130 + !text: tr('EQ Manager') + + Button + id: setup + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 3 + height: 17 + text: Setup +]]) +ui:setId(panelName) + +if not storage[panelName] then + storage[panelName] = { + enabled = false, + rules = {} + } +end + +local config = storage[panelName] + +ui.switch:setOn(config.enabled) +ui.switch.onClick = function(widget) + config.enabled = not config.enabled + widget:setOn(config.enabled) +end + +local conditions = { -- always add new conditions at the bottom + "Item is available and not worn.", -- nothing 1 + "Monsters around is more than: ", -- spinbox 2 + "Monsters around is less than: ", -- spinbox 3 + "Health precent is below:", -- spinbox 4 + "Health precent is above:", -- spinbox 5 + "Mana precent is below:", -- spinbox 6 + "Mana precent is above:", -- spinbox 7 + "Target name is:", -- BotTextEdit 8 + "Hotkey is being pressed:", -- BotTextEdit 9 + "Player is paralyzed", -- nothing 10 + "Player is in protection zone", -- nothing 11 + "Players around is more than:", -- spinbox 12 + "Players around is less than:", -- spinbox 13 + "TargetBot Danger is Above:", -- spinbox 14 + "Blacklist player in range (sqm)" -- spinbox 15 +} + +local conditionNumber = 1 +local optionalConditionNumber = 2 + +local mainWindow = UI.createWindow("EquipWindow") +mainWindow:hide() + +ui.setup.onClick = function() + mainWindow:show() + mainWindow:raise() + mainWindow:focus() +end + +mainWindow.closeButton.onClick = function() + mainWindow:hide() + resetFields() +end + +local inputPanel = mainWindow.inputPanel +local listPanel = mainWindow.listPanel + +inputPanel.optionalCondition:hide() +inputPanel.useSecondCondition.onOptionChange = function(widget, option, data) + if option ~= "-" then + inputPanel.optionalCondition:show() + else + inputPanel.optionalCondition:hide() + end +end + +inputPanel.unequip.onClick = function() + local value = 115 + local panel = inputPanel.unequipPanel + local height = panel:getHeight() + if height == 0 then + panel:setHeight(value) + mainWindow:setHeight(mainWindow:getHeight()+value) + inputPanel:setHeight(inputPanel:getHeight()+value) + listPanel:setHeight(listPanel:getHeight()+value) + else + panel:setHeight(0) + mainWindow:setHeight(mainWindow:getHeight()-value) + inputPanel:setHeight(inputPanel:getHeight()-value) + listPanel:setHeight(listPanel:getHeight()-value) + end +end + +local function setCondition(first, n) + local widget + local spinBox + local textEdit + + if first then + widget = inputPanel.condition.description.text + spinBox = inputPanel.condition.spinbox + textEdit = inputPanel.condition.text + else + widget = inputPanel.optionalCondition.description.text + spinBox = inputPanel.optionalCondition.spinbox + textEdit = inputPanel.optionalCondition.text + end + + -- reset values after change + spinBox:setValue(0) + textEdit:setText('') + + if n == 1 or n == 10 or n == 11 then + spinBox:hide() + textEdit:hide() + elseif n == 9 or n == 8 then + spinBox:hide() + textEdit:show() + if n == 9 then + textEdit:setWidth(75) + else + textEdit:setWidth(200) + end + else + spinBox:show() + textEdit:hide() + end + widget:setText(conditions[n]) +end + +-- add default text & windows +setCondition(true, 1) +setCondition(false, 2) + +-- in/de/crementation buttons +inputPanel.condition.nex.onClick = function() + local max = #conditions + + if inputPanel.optionalCondition:isVisible() then + if conditionNumber == max then + if optionalConditionNumber == 1 then + conditionNumber = 2 + else + conditionNumber = 1 + end + else + local futureNumber = conditionNumber + 1 + local safeFutureNumber = conditionNumber + 2 > max and 1 or conditionNumber + 2 + conditionNumber = futureNumber ~= optionalConditionNumber and futureNumber or safeFutureNumber + end + else + conditionNumber = conditionNumber == max and 1 or conditionNumber + 1 + if optionalConditionNumber == conditionNumber then + optionalConditionNumber = optionalConditionNumber == max and 1 or optionalConditionNumber + 1 + setCondition(false, optionalConditionNumber) + end + end + setCondition(true, conditionNumber) +end + +inputPanel.condition.pre.onClick = function() + local max = #conditions + + if inputPanel.optionalCondition:isVisible() then + if conditionNumber == 1 then + if optionalConditionNumber == max then + conditionNumber = max-1 + else + conditionNumber = max + end + else + local futureNumber = conditionNumber - 1 + local safeFutureNumber = conditionNumber - 2 < 1 and max or conditionNumber - 2 + conditionNumber = futureNumber ~= optionalConditionNumber and futureNumber or safeFutureNumber + end + else + conditionNumber = conditionNumber == 1 and max or conditionNumber - 1 + if optionalConditionNumber == conditionNumber then + optionalConditionNumber = optionalConditionNumber == 1 and max or optionalConditionNumber - 1 + setCondition(false, optionalConditionNumber) + end + end + setCondition(true, conditionNumber) +end + +inputPanel.optionalCondition.nex.onClick = function() + local max = #conditions + + if optionalConditionNumber == max then + if conditionNumber == 1 then + optionalConditionNumber = 2 + else + optionalConditionNumber = 1 + end + else + local futureNumber = optionalConditionNumber + 1 + local safeFutureNumber = optionalConditionNumber + 2 > max and 1 or optionalConditionNumber + 2 + optionalConditionNumber = futureNumber ~= conditionNumber and futureNumber or safeFutureNumber + end + setCondition(false, optionalConditionNumber) +end + +inputPanel.optionalCondition.pre.onClick = function() + local max = #conditions + + if optionalConditionNumber == 1 then + if conditionNumber == max then + optionalConditionNumber = max-1 + else + optionalConditionNumber = max + end + else + local futureNumber = optionalConditionNumber - 1 + local safeFutureNumber = optionalConditionNumber - 2 < 1 and max or optionalConditionNumber - 2 + optionalConditionNumber = futureNumber ~= conditionNumber and futureNumber or safeFutureNumber + end + setCondition(false, optionalConditionNumber) +end + +listPanel.up.onClick = function(widget) + local focused = listPanel.list:getFocusedChild() + local n = listPanel.list:getChildIndex(focused) + local t = config.rules + + t[n], t[n-1] = t[n-1], t[n] + if n-1 == 1 then + widget:setEnabled(false) + end + listPanel.down:setEnabled(true) + listPanel.list:moveChildToIndex(focused, n-1) + listPanel.list:ensureChildVisible(focused) +end + +listPanel.down.onClick = function(widget) + local focused = listPanel.list:getFocusedChild() + local n = listPanel.list:getChildIndex(focused) + local t = config.rules + + t[n], t[n+1] = t[n+1], t[n] + if n + 1 == listPanel.list:getChildCount() then + widget:setEnabled(false) + end + listPanel.up:setEnabled(true) + listPanel.list:moveChildToIndex(focused, n+1) + listPanel.list:ensureChildVisible(focused) + end + +function getItemsFromBox() + local t = {} + + for i, child in ipairs(inputPanel.itemBox:getChildren()) do + local id = child:getItemId() + if id > 100 then + table.insert(t, id) + end + end + return t +end + +function refreshItemBox(reset) + local max = 8 + local box = inputPanel.itemBox + local childAmount = box:getChildCount() + + --height + if #getItemsFromBox() < 7 then + mainWindow:setHeight(345) + inputPanel:setHeight(265) + listPanel:setHeight(265) + box:setHeight(40) + else + mainWindow:setHeight(370) + inputPanel:setHeight(300) + listPanel:setHeight(300) + box:setHeight(80) + end + + if reset then + box:destroyChildren() + local widget = UI.createWidget("BotItem", box) + widget.onItemChange = function(widget) + local id = widget:getItemId() + local index = box:getChildIndex(widget) + if id < 100 or (table.find(getItemsFromBox(), id) ~= index) then + widget:destroy() + end + refreshItemBox() + end + return + end + + if childAmount == 0 then + local widget = UI.createWidget("BotItem", box) + widget.onItemChange = function(widget) + local id = widget:getItemId() + local index = box:getChildIndex(widget) + if id < 100 or (table.find(getItemsFromBox(), id) ~= index) then + widget:destroy() + end + refreshItemBox() + end + elseif box:getLastChild():getItemId() > 100 and childAmount <= max then + local widget = UI.createWidget("BotItem", box) + widget.onItemChange = function(widget) + local id = widget:getItemId() + local index = box:getChildIndex(widget) + if id < 100 or (table.find(getItemsFromBox(), id) ~= index) then + widget:destroy() + end + refreshItemBox() + end + end +end +refreshItemBox() + +local function resetFields() + refreshItemBox(true) + inputPanel.name:setText('') + conditionNumber = 1 + optionalConditionNumber = 2 + setCondition(false, optionalConditionNumber) + setCondition(true, conditionNumber) + inputPanel.useSecondCondition:setCurrentOption("-") + for i, child in pairs(inputPanel.unequipPanel:getChildren()) do + child:setChecked(false) + end +end + +-- buttons disabled by default +listPanel.up:setEnabled(false) +listPanel.down:setEnabled(false) +function refreshRules() + local list = listPanel.list + + list:destroyChildren() + for i,v in pairs(config.rules) do + local widget = UI.createWidget('Rule', list) + widget:setId(v.name) + widget:setText(v.name) + widget.remove.onClick = function() + widget:destroy() + table.remove(config.rules, table.find(config.rules, v)) + listPanel.up:setEnabled(false) + listPanel.down:setEnabled(false) + refreshRules() + end + widget.visible:setColor(v.visible and "green" or "red") + widget.visible.onClick = function() + v.visible = not v.visible + widget.visible:setColor(v.visible and "green" or "red") + end + widget.enabled:setChecked(v.enabled) + widget.enabled.onClick = function() + v.enabled = not v.enabled + widget.enabled:setChecked(v.enabled) + end + local desc + for i, v in ipairs(v.items) do + if i == 1 then + desc = "items: " .. v + else + desc = desc .. ", " .. v + end + end + widget:setTooltip(desc) + widget.onClick = function() + local panel = listPanel + if #panel.list:getChildren() == 1 then + panel.up:setEnabled(false) + panel.down:setEnabled(false) + elseif panel.list:getChildIndex(panel.list:getFocusedChild()) == 1 then + panel.up:setEnabled(false) + panel.down:setEnabled(true) + elseif panel.list:getChildIndex(panel.list:getFocusedChild()) == #panel.list:getChildren() then + panel.up:setEnabled(true) + panel.down:setEnabled(false) + else + panel.up:setEnabled(true) + panel.down:setEnabled(true) + end + end + widget.onDoubleClick = function() + -- main + conditionNumber = v.mainCondition + setCondition(true, conditionNumber) + if conditionNumber == 8 or conditionNumber == 9 then + inputPanel.condition.text:setText(v.mainValue) + elseif conditionNumber ~= 1 then + inputPanel.condition.spinbox:setValue(v.mainValue) + end + -- relation + inputPanel.useSecondCondition:setCurrentOption(v.relation) + -- optional + if v.relation ~= "-" then + optionalConditionNumber = v.optionalCondition + setCondition(false, optionalConditionNumber) + if optionalConditionNumber == 8 or optionalConditionNumber == 9 then + inputPanel.optionalCondition.text:setText(v.optValue) + elseif optionalConditionNumber ~= 1 then + inputPanel.optionalCondition.spinbox:setValue(v.optValue) + end + end + -- name + inputPanel.name:setText(v.name) + -- items + inputPanel.itemBox:destroyChildren() + for i, item in ipairs(v.items) do + local widget = UI.createWidget("BotItem", inputPanel.itemBox) + widget:setItemId(item) + widget.onItemChange = function(widget) + local id = widget:getItemId() + local index = box:getChildIndex(widget) + if id < 100 or (table.find(getItemsFromBox(), id) ~= index) then + widget:destroy() + end + refreshItemBox() + end + end + -- unequip + if type(v.unequip) == "table" then + for i, tick in ipairs(v.unequip) do + local checkbox = inputPanel.unequipPanel:getChildren()[i] + checkbox:setChecked(tick) + end + end + refreshItemBox() + -- remove value + table.remove(config.rules, table.find(config.rules, v)) + refreshRules() + end + end +end +refreshRules() + +inputPanel.addButton.onClick = function() + local mainVal + local optVal + local relation = inputPanel.useSecondCondition:getText() + local name = inputPanel.name:getText() + local items = getItemsFromBox() + local unequip = {} + local hasUnequip = false + + for i, child in pairs(inputPanel.unequipPanel:getChildren()) do + if child:isChecked() then + table.insert(unequip, true) + hasUnequip = true + else + table.insert(unequip, false) + end + end + + if conditionNumber == 1 then + mainVal = nil + elseif conditionNumber == 8 then + mainVal = inputPanel.condition.text:getText() + if mainVal:len() == 0 then + return warn("[vBot Equipper] Please fill the name of the creature.") + end + elseif conditionNumber == 9 then + mainVal = inputPanel.condition.text:getText() + if mainVal:len() == 0 then + return warn("[vBot Equipper] Please set correct hotkey.") + end + else + mainVal = inputPanel.condition.spinbox:getValue() + end + + if relation ~= "-" then + if optionalConditionNumber == 1 then + optVal = nil + elseif optionalConditionNumber == 8 then + optVal = inputPanel.optionalCondition.text:getText() + if optVal:len() == 0 then + return warn("[vBot Equipper] Please fill the name of the creature.") + end + elseif optionalConditionNumber == 9 then + optVal = inputPanel.optionalCondition.text:getText() + if optVal:len() == 0 then + return warn("[vBot Equipper] Please set correct hotkey.") + end + else + optVal = inputPanel.optionalCondition.spinbox:getValue() + end + end + + if #items == 0 and not hasUnequip then + return warn("[vBot Equipper] Please add items or select unequip slots.") + end + + if #name == 0 then + return warn("[vBot Equipper] Please fill name of the profile.") + end + for i, child in pairs(listPanel.list:getChildren()) do + if child:getText() == name then + return warn("[vBot Equipper] There is already rule with this name! Choose different or remove old one.") + end + end + + -- add + table.insert(config.rules, { + enabled = true, + visible = true, + mainCondition = conditionNumber, + optionalCondition = optionalConditionNumber, + mainValue = mainVal, + optValue = optVal, + relation = relation, + items = items, + name = name, + unequip = unequip + }) + + refreshRules() + resetFields() +end + +--"Item is available and not worn.", -- nothing 1 +--"Monsters around is more than: ", -- spinbox 2 +--"Monsters around is less than: ", -- spinbox 3 +--"Health precent is below:", -- spinbox 4 +--"Health precent is above:", -- spinbox 5 +--"Mana precent is below:", -- spinbox 6 +--"Mana precent is above:", -- spinbox 7 +--"Target name is:", -- BotTextEdit 8 +--"Hotkey is being pressed:", -- Button 9 +--"Player is paralyzed", -- nothing 10 + +local pressedKey = "" +local lastPress = now +onKeyPress(function(keys) + pressedKey = keys + lastPress = now + schedule(100, function() + if now - lastPress > 20 then + pressedKey = "" + end + end) +end) + +local function interpreteCondition(n, v) + + if n == 1 then + return true + elseif n == 2 then + return getMonsters() > v + elseif n == 3 then + return getMonsters() < v + elseif n == 4 then + return hppercent() < v + elseif n == 5 then + return hppercent() > v + elseif n == 6 then + return manapercent() < v + elseif n == 7 then + return manapercent() > v + elseif n == 8 then + return target() and target():getName():lower() == v:lower() or false + elseif n == 9 then + return pressedKey == v + elseif n == 10 then + return isParalyzed() + elseif n == 11 then + return isInPz() + elseif n == 12 then + return getPlayers() > v + elseif n == 13 then + return getPlayers() < v + elseif n == 14 then + return TargetBot.Danger() > v and TargetBot.isOn() + elseif n == 15 then + return isBlackListedPlayerInRange(v) + end + +end + +local function finalCheck(first,relation,second) + if relation == "-" then + return first + elseif relation == "and" then + return first and second + elseif relation == "or" then + return first or second + end +end + +local function isEquipped(id) + local t = {getNeck(), getHead(), getBody(), getRight(), getLeft(), getLeg(), getFeet(), getFinger(), getAmmo()} + local ids = {id, getInactiveItemId(id), getActiveItemId(id)} + + for i, slot in pairs(t) do + if slot and table.find(ids, slot:getId()) then + return true + end + end + return false +end + +local function unequipItem(table) + --[[ + head + neck + torso + left + right + legs + finger + ammo slot + boots + ]] + local slots = {getHead(), getNeck(), getBody(), getLeft(), getRight(), getLeg(), getFinger(), getAmmo(), getFeet()} + + if type(table) ~= "table" then return end + for i, slot in pairs(table) do + local physicalSlot = slots[i] + + if slot and physicalSlot then + g_game.equipItemId(physicalSlot:getId()) + return true + end + end + return false +end + +EquipManager = macro(50, function() + if not config.enabled then return end + if #config.rules == 0 then return end + + for i, rule in ipairs(config.rules) do + local widget = listPanel.list:getChildById(rule.name) + if mainWindow:isVisible() then + for i, child in ipairs(listPanel.list:getChildren()) do + if child ~= widget then + child:setColor('white') + end + end + end + if rule.enabled then + widget:setColor('green') + local firstCondition = interpreteCondition(rule.mainCondition, rule.mainValue) + local optionalCondition = nil + if rule.relation ~= "-" then + optionalCondition = interpreteCondition(rule.optionalCondition, rule.optValue) + end + + if finalCheck(firstCondition, rule.relation, optionalCondition) then + if unequipItem(rule.unequip) == true then + delay(200) + return + end + for i, item in ipairs(rule.items) do + if not isEquipped(item) then + if rule.visible then + if itemAmount(item) > 0 then + delay(200) + return g_game.equipItemId(item) + end + else + delay(200) + return g_game.equipItemId(item) + end + end + end + return + end + end + end + pressedKey = "" +end) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/HealBot.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/HealBot.lua new file mode 100644 index 0000000..530f429 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/HealBot.lua @@ -0,0 +1,712 @@ +local standBySpells = false +local standByItems = false + +local red = "#ff0800" -- "#ff0800" / #ea3c53 best +local blue = "#7ef9ff" + +setDefaultTab("HP") +local healPanelName = "healbot" +local ui = setupUI([[ +Panel + height: 38 + + BotSwitch + id: title + anchors.top: parent.top + anchors.left: parent.left + text-align: center + width: 130 + !text: tr('HealBot') + + Button + id: settings + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 3 + height: 17 + text: Setup + + Button + id: 1 + anchors.top: prev.bottom + anchors.left: parent.left + text: 1 + margin-right: 2 + margin-top: 4 + size: 17 17 + + Button + id: 2 + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + text: 2 + margin-left: 4 + size: 17 17 + + Button + id: 3 + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + text: 3 + margin-left: 4 + size: 17 17 + + Button + id: 4 + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + text: 4 + margin-left: 4 + size: 17 17 + + Button + id: 5 + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + text: 5 + margin-left: 4 + size: 17 17 + + Label + id: name + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + anchors.right: parent.right + text-align: center + margin-left: 4 + height: 17 + text: Profile #1 + background: #292A2A +]]) +ui:setId(healPanelName) + +if not HealBotConfig[healPanelName] or not HealBotConfig[healPanelName][1] or #HealBotConfig[healPanelName] ~= 5 then + HealBotConfig[healPanelName] = { + [1] = { + enabled = false, + spellTable = {}, + itemTable = {}, + name = "Profile #1", + Visible = true, + Cooldown = true, + Interval = true, + Conditions = true, + Delay = true, + MessageDelay = false + }, + [2] = { + enabled = false, + spellTable = {}, + itemTable = {}, + name = "Profile #2", + Visible = true, + Cooldown = true, + Interval = true, + Conditions = true, + Delay = true, + MessageDelay = false + }, + [3] = { + enabled = false, + spellTable = {}, + itemTable = {}, + name = "Profile #3", + Visible = true, + Cooldown = true, + Interval = true, + Conditions = true, + Delay = true, + MessageDelay = false + }, + [4] = { + enabled = false, + spellTable = {}, + itemTable = {}, + name = "Profile #4", + Visible = true, + Cooldown = true, + Interval = true, + Conditions = true, + Delay = true, + MessageDelay = false + }, + [5] = { + enabled = false, + spellTable = {}, + itemTable = {}, + name = "Profile #5", + Visible = true, + Cooldown = true, + Interval = true, + Conditions = true, + Delay = true, + MessageDelay = false + }, + } +end + +if not HealBotConfig.currentHealBotProfile or HealBotConfig.currentHealBotProfile == 0 or HealBotConfig.currentHealBotProfile > 5 then + HealBotConfig.currentHealBotProfile = 1 +end + +-- finding correct table, manual unfortunately +local currentSettings +local setActiveProfile = function() + local n = HealBotConfig.currentHealBotProfile + currentSettings = HealBotConfig[healPanelName][n] +end +setActiveProfile() + +local activeProfileColor = function() + for i=1,5 do + if i == HealBotConfig.currentHealBotProfile then + ui[i]:setColor("green") + else + ui[i]:setColor("white") + end + end +end +activeProfileColor() + +ui.title:setOn(currentSettings.enabled) +ui.title.onClick = function(widget) + currentSettings.enabled = not currentSettings.enabled + widget:setOn(currentSettings.enabled) + vBotConfigSave("heal") +end + +ui.settings.onClick = function(widget) + healWindow:show() + healWindow:raise() + healWindow:focus() +end + +rootWidget = g_ui.getRootWidget() +if rootWidget then + healWindow = UI.createWindow('HealWindow', rootWidget) + healWindow:hide() + + healWindow.onVisibilityChange = function(widget, visible) + if not visible then + vBotConfigSave("heal") + healWindow.healer:show() + healWindow.settings:hide() + healWindow.settingsButton:setText("Settings") + end + end + + healWindow.settingsButton.onClick = function(widget) + if healWindow.healer:isVisible() then + healWindow.healer:hide() + healWindow.settings:show() + widget:setText("Back") + else + healWindow.healer:show() + healWindow.settings:hide() + widget:setText("Settings") + end + end + + local setProfileName = function() + ui.name:setText(currentSettings.name) + end + healWindow.settings.profiles.Name.onTextChange = function(widget, text) + currentSettings.name = text + setProfileName() + end + healWindow.settings.list.Visible.onClick = function(widget) + currentSettings.Visible = not currentSettings.Visible + healWindow.settings.list.Visible:setChecked(currentSettings.Visible) + end + healWindow.settings.list.Cooldown.onClick = function(widget) + currentSettings.Cooldown = not currentSettings.Cooldown + healWindow.settings.list.Cooldown:setChecked(currentSettings.Cooldown) + end + healWindow.settings.list.Interval.onClick = function(widget) + currentSettings.Interval = not currentSettings.Interval + healWindow.settings.list.Interval:setChecked(currentSettings.Interval) + end + healWindow.settings.list.Conditions.onClick = function(widget) + currentSettings.Conditions = not currentSettings.Conditions + healWindow.settings.list.Conditions:setChecked(currentSettings.Conditions) + end + healWindow.settings.list.Delay.onClick = function(widget) + currentSettings.Delay = not currentSettings.Delay + healWindow.settings.list.Delay:setChecked(currentSettings.Delay) + end + healWindow.settings.list.MessageDelay.onClick = function(widget) + currentSettings.MessageDelay = not currentSettings.MessageDelay + healWindow.settings.list.MessageDelay:setChecked(currentSettings.MessageDelay) + end + + local refreshSpells = function() + if currentSettings.spellTable then + healWindow.healer.spells.spellList:destroyChildren() + for _, entry in pairs(currentSettings.spellTable) do + local label = UI.createWidget("SpellEntry", healWindow.healer.spells.spellList) + label.enabled:setChecked(entry.enabled) + label.enabled.onClick = function(widget) + standBySpells = false + standByItems = false + entry.enabled = not entry.enabled + label.enabled:setChecked(entry.enabled) + end + label.remove.onClick = function(widget) + standBySpells = false + standByItems = false + table.removevalue(currentSettings.spellTable, entry) + reindexTable(currentSettings.spellTable) + label:destroy() + end + label:setText("(MP>" .. entry.cost .. ") " .. entry.origin .. entry.sign .. entry.value .. ": " .. entry.spell) + end + end + end + refreshSpells() + + local refreshItems = function() + if currentSettings.itemTable then + healWindow.healer.items.itemList:destroyChildren() + for _, entry in pairs(currentSettings.itemTable) do + local label = UI.createWidget("ItemEntry", healWindow.healer.items.itemList) + label.enabled:setChecked(entry.enabled) + label.enabled.onClick = function(widget) + standBySpells = false + standByItems = false + entry.enabled = not entry.enabled + label.enabled:setChecked(entry.enabled) + end + label.remove.onClick = function(widget) + standBySpells = false + standByItems = false + table.removevalue(currentSettings.itemTable, entry) + reindexTable(currentSettings.itemTable) + label:destroy() + end + label.id:setItemId(entry.item) + label:setText(entry.origin .. entry.sign .. entry.value .. ": " .. entry.item) + end + end + end + refreshItems() + + healWindow.healer.spells.MoveUp.onClick = function(widget) + local input = healWindow.healer.spells.spellList:getFocusedChild() + if not input then return end + local index = healWindow.healer.spells.spellList:getChildIndex(input) + if index < 2 then return end + + local t = currentSettings.spellTable + + t[index],t[index-1] = t[index-1], t[index] + healWindow.healer.spells.spellList:moveChildToIndex(input, index - 1) + healWindow.healer.spells.spellList:ensureChildVisible(input) + end + + healWindow.healer.spells.MoveDown.onClick = function(widget) + local input = healWindow.healer.spells.spellList:getFocusedChild() + if not input then return end + local index = healWindow.healer.spells.spellList:getChildIndex(input) + if index >= healWindow.healer.spells.spellList:getChildCount() then return end + + local t = currentSettings.spellTable + + t[index],t[index+1] = t[index+1],t[index] + healWindow.healer.spells.spellList:moveChildToIndex(input, index + 1) + healWindow.healer.spells.spellList:ensureChildVisible(input) + end + + healWindow.healer.items.MoveUp.onClick = function(widget) + local input = healWindow.healer.items.itemList:getFocusedChild() + if not input then return end + local index = healWindow.healer.items.itemList:getChildIndex(input) + if index < 2 then return end + + local t = currentSettings.itemTable + + t[index],t[index-1] = t[index-1], t[index] + healWindow.healer.items.itemList:moveChildToIndex(input, index - 1) + healWindow.healer.items.itemList:ensureChildVisible(input) + end + + healWindow.healer.items.MoveDown.onClick = function(widget) + local input = healWindow.healer.items.itemList:getFocusedChild() + if not input then return end + local index = healWindow.healer.items.itemList:getChildIndex(input) + if index >= healWindow.healer.items.itemList:getChildCount() then return end + + local t = currentSettings.itemTable + + t[index],t[index+1] = t[index+1],t[index] + healWindow.healer.items.itemList:moveChildToIndex(input, index + 1) + healWindow.healer.items.itemList:ensureChildVisible(input) + end + + healWindow.healer.spells.addSpell.onClick = function(widget) + + local spellFormula = healWindow.healer.spells.spellFormula:getText():trim() + local manaCost = tonumber(healWindow.healer.spells.manaCost:getText()) + local spellTrigger = tonumber(healWindow.healer.spells.spellValue:getText()) + local spellSource = healWindow.healer.spells.spellSource:getCurrentOption().text + local spellEquasion = healWindow.healer.spells.spellCondition:getCurrentOption().text + local source + local equasion + + if not manaCost then + warn("HealBot: incorrect mana cost value!") + healWindow.healer.spells.spellFormula:setText('') + healWindow.healer.spells.spellValue:setText('') + healWindow.healer.spells.manaCost:setText('') + return + end + if not spellTrigger then + warn("HealBot: incorrect condition value!") + healWindow.healer.spells.spellFormula:setText('') + healWindow.healer.spells.spellValue:setText('') + healWindow.healer.spells.manaCost:setText('') + return + end + + if spellSource == "Current Mana" then + source = "MP" + elseif spellSource == "Current Health" then + source = "HP" + elseif spellSource == "Mana Percent" then + source = "MP%" + elseif spellSource == "Health Percent" then + source = "HP%" + else + source = "burst" + end + + if spellEquasion == "Above" then + equasion = ">" + elseif spellEquasion == "Below" then + equasion = "<" + else + equasion = "=" + end + + if spellFormula:len() > 0 then + table.insert(currentSettings.spellTable, {index = #currentSettings.spellTable+1, spell = spellFormula, sign = equasion, origin = source, cost = manaCost, value = spellTrigger, enabled = true}) + healWindow.healer.spells.spellFormula:setText('') + healWindow.healer.spells.spellValue:setText('') + healWindow.healer.spells.manaCost:setText('') + end + standBySpells = false + standByItems = false + refreshSpells() + end + + healWindow.healer.items.addItem.onClick = function(widget) + + local id = healWindow.healer.items.itemId:getItemId() + local trigger = tonumber(healWindow.healer.items.itemValue:getText()) + local src = healWindow.healer.items.itemSource:getCurrentOption().text + local eq = healWindow.healer.items.itemCondition:getCurrentOption().text + local source + local equasion + + if not trigger then + warn("HealBot: incorrect trigger value!") + healWindow.healer.items.itemId:setItemId(0) + healWindow.healer.items.itemValue:setText('') + return + end + + if src == "Current Mana" then + source = "MP" + elseif src == "Current Health" then + source = "HP" + elseif src == "Mana Percent" then + source = "MP%" + elseif src == "Health Percent" then + source = "HP%" + else + source = "burst" + end + + if eq == "Above" then + equasion = ">" + elseif eq == "Below" then + equasion = "<" + else + equasion = "=" + end + + if id > 100 then + table.insert(currentSettings.itemTable, {index = #currentSettings.itemTable+1,item = id, sign = equasion, origin = source, value = trigger, enabled = true}) + standBySpells = false + standByItems = false + refreshItems() + healWindow.healer.items.itemId:setItemId(0) + healWindow.healer.items.itemValue:setText('') + end + end + + healWindow.closeButton.onClick = function(widget) + healWindow:hide() + end + + local loadSettings = function() + ui.title:setOn(currentSettings.enabled) + setProfileName() + healWindow.settings.profiles.Name:setText(currentSettings.name) + refreshSpells() + refreshItems() + healWindow.settings.list.Visible:setChecked(currentSettings.Visible) + healWindow.settings.list.Cooldown:setChecked(currentSettings.Cooldown) + healWindow.settings.list.Delay:setChecked(currentSettings.Delay) + healWindow.settings.list.MessageDelay:setChecked(currentSettings.MessageDelay) + healWindow.settings.list.Interval:setChecked(currentSettings.Interval) + healWindow.settings.list.Conditions:setChecked(currentSettings.Conditions) + end + loadSettings() + + local profileChange = function() + setActiveProfile() + activeProfileColor() + loadSettings() + vBotConfigSave("heal") + end + + local resetSettings = function() + currentSettings.enabled = false + currentSettings.spellTable = {} + currentSettings.itemTable = {} + currentSettings.Visible = true + currentSettings.Cooldown = true + currentSettings.Delay = true + currentSettings.MessageDelay = false + currentSettings.Interval = true + currentSettings.Conditions = true + currentSettings.name = "Profile #" .. HealBotConfig.currentBotProfile + end + + -- profile buttons + for i=1,5 do + local button = ui[i] + button.onClick = function() + HealBotConfig.currentHealBotProfile = i + profileChange() + end + end + + healWindow.settings.profiles.ResetSettings.onClick = function() + resetSettings() + loadSettings() + end + + + -- public functions + HealBot = {} -- global table + + HealBot.isOn = function() + return currentSettings.enabled + end + + HealBot.isOff = function() + return not currentSettings.enabled + end + + HealBot.setOff = function() + currentSettings.enabled = false + ui.title:setOn(currentSettings.enabled) + vBotConfigSave("atk") + end + + HealBot.setOn = function() + currentSettings.enabled = true + ui.title:setOn(currentSettings.enabled) + vBotConfigSave("atk") + end + + HealBot.getActiveProfile = function() + return HealBotConfig.currentHealBotProfile -- returns number 1-5 + end + + HealBot.setActiveProfile = function(n) + if not n or not tonumber(n) or n < 1 or n > 5 then + return error("[HealBot] wrong profile parameter! should be 1 to 5 is " .. n) + else + HealBotConfig.currentHealBotProfile = n + profileChange() + end + end + + HealBot.show = function() + healWindow:show() + healWindow:raise() + healWindow:focus() + end +end + +-- spells +macro(100, function() + if standBySpells then return end + if not currentSettings.enabled then return end + local somethingIsOnCooldown = false + + for _, entry in pairs(currentSettings.spellTable) do + if entry.enabled and entry.cost < mana() then + if canCast(entry.spell, not currentSettings.Conditions, not currentSettings.Cooldown) then + if entry.origin == "HP%" then + if entry.sign == "=" and hppercent() == entry.value then + say(entry.spell) + return + elseif entry.sign == ">" and hppercent() >= entry.value then + say(entry.spell) + return + elseif entry.sign == "<" and hppercent() <= entry.value then + say(entry.spell) + return + end + elseif entry.origin == "HP" then + if entry.sign == "=" and hp() == entry.value then + say(entry.spell) + return + elseif entry.sign == ">" and hp() >= entry.value then + say(entry.spell) + return + elseif entry.sign == "<" and hp() <= entry.value then + say(entry.spell) + return + end + elseif entry.origin == "MP%" then + if entry.sign == "=" and manapercent() == entry.value then + say(entry.spell) + return + elseif entry.sign == ">" and manapercent() >= entry.value then + say(entry.spell) + return + elseif entry.sign == "<" and manapercent() <= entry.value then + say(entry.spell) + return + end + elseif entry.origin == "MP" then + if entry.sign == "=" and mana() == entry.value then + say(entry.spell) + return + elseif entry.sign == ">" and mana() >= entry.value then + say(entry.spell) + return + elseif entry.sign == "<" and mana() <= entry.value then + say(entry.spell) + return + end + elseif entry.origin == "burst" then + if entry.sign == "=" and burstDamageValue() == entry.value then + say(entry.spell) + return + elseif entry.sign == ">" and burstDamageValue() >= entry.value then + say(entry.spell) + return + elseif entry.sign == "<" and burstDamageValue() <= entry.value then + say(entry.spell) + return + end + end + else + somethingIsOnCooldown = true + end + end + end + if not somethingIsOnCooldown then + standBySpells = true + end +end) + +-- items +macro(100, function() + if standByItems then return end + if not currentSettings.enabled or #currentSettings.itemTable == 0 then return end + if currentSettings.Delay and vBot.isUsing then return end + if currentSettings.MessageDelay and vBot.isUsingPotion then return end + + if not currentSettings.MessageDelay then + delay(400) + end + + if TargetBot.isOn() and TargetBot.Looting.getStatus():len() > 0 and currentSettings.Interval then + if not currentSettings.MessageDelay then + delay(700) + else + delay(200) + end + end + + for _, entry in pairs(currentSettings.itemTable) do + local item = findItem(entry.item) + if (not currentSettings.Visible or item) and entry.enabled then + if entry.origin == "HP%" then + if entry.sign == "=" and hppercent() == entry.value then + g_game.useInventoryItemWith(entry.item, player) + return + elseif entry.sign == ">" and hppercent() >= entry.value then + g_game.useInventoryItemWith(entry.item, player) + return + elseif entry.sign == "<" and hppercent() <= entry.value then + g_game.useInventoryItemWith(entry.item, player) + return + end + elseif entry.origin == "HP" then + if entry.sign == "=" and hp() == tonumberentry.value then + g_game.useInventoryItemWith(entry.item, player) + return + elseif entry.sign == ">" and hp() >= entry.value then + g_game.useInventoryItemWith(entry.item, player) + return + elseif entry.sign == "<" and hp() <= entry.value then + g_game.useInventoryItemWith(entry.item, player) + return + end + elseif entry.origin == "MP%" then + if entry.sign == "=" and manapercent() == entry.value then + g_game.useInventoryItemWith(entry.item, player) + return + elseif entry.sign == ">" and manapercent() >= entry.value then + g_game.useInventoryItemWith(entry.item, player) + return + elseif entry.sign == "<" and manapercent() <= entry.value then + g_game.useInventoryItemWith(entry.item, player) + return + end + elseif entry.origin == "MP" then + if entry.sign == "=" and mana() == entry.value then + g_game.useInventoryItemWith(entry.item, player) + return + elseif entry.sign == ">" and mana() >= entry.value then + g_game.useInventoryItemWith(entry.item, player) + return + elseif entry.sign == "<" and mana() <= entry.value then + g_game.useInventoryItemWith(entry.item, player) + return + end + elseif entry.origin == "burst" then + if entry.sign == "=" and burstDamageValue() == entry.value then + g_game.useInventoryItemWith(entry.item, player) + return + elseif entry.sign == ">" and burstDamageValue() >= entry.value then + g_game.useInventoryItemWith(entry.item, player) + return + elseif entry.sign == "<" and burstDamageValue() <= entry.value then + g_game.useInventoryItemWith(entry.item, player) + return + end + end + end + end + standByItems = true +end) +UI.Separator() + +onPlayerHealthChange(function(healthPercent) + standByItems = false + standBySpells = false +end) + +onManaChange(function(player, mana, maxMana, oldMana, oldMaxMana) + standByItems = false + standBySpells = false +end) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/HealBot.otui b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/HealBot.otui new file mode 100644 index 0000000..fb8cb03 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/HealBot.otui @@ -0,0 +1,492 @@ +SettingCheckBox < CheckBox + text-wrap: true + text-auto-resize: true + margin-top: 3 + font: verdana-11px-rounded + +SpellSourceBoxPopupMenu < ComboBoxPopupMenu +SpellSourceBoxPopupMenuButton < ComboBoxPopupMenuButton +SpellSourceBox < ComboBox + @onSetup: | + self:addOption("Current Mana") + self:addOption("Current Health") + self:addOption("Mana Percent") + self:addOption("Health Percent") + self:addOption("Burst Damage") + +SpellConditionBoxPopupMenu < ComboBoxPopupMenu +SpellConditionBoxPopupMenuButton < ComboBoxPopupMenuButton +SpellConditionBox < ComboBox + @onSetup: | + self:addOption("Below") + self:addOption("Above") + self:addOption("Equal To") + +SpellEntry < Label + background-color: alpha + text-offset: 18 1 + focusable: true + height: 16 + font: verdana-11px-rounded + + CheckBox + id: enabled + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + width: 15 + height: 15 + margin-top: 2 + margin-left: 3 + + $focus: + background-color: #00000055 + + Button + id: remove + !text: tr('x') + anchors.right: parent.right + margin-right: 15 + text-offset: 1 0 + width: 15 + height: 15 + +ItemEntry < Label + background-color: alpha + text-offset: 40 1 + focusable: true + height: 16 + font: verdana-11px-rounded + + CheckBox + id: enabled + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + width: 15 + height: 15 + margin-top: 2 + margin-left: 3 + + UIItem + id: id + anchors.left: prev.right + margin-left: 3 + anchors.verticalCenter: parent.verticalCenter + size: 15 15 + focusable: false + + $focus: + background-color: #00000055 + + Button + id: remove + !text: tr('x') + anchors.right: parent.right + margin-right: 15 + text-offset: 1 0 + width: 15 + height: 15 + +SpellHealing < FlatPanel + size: 490 130 + + Label + id: title + anchors.verticalCenter: parent.top + anchors.left: parent.left + margin-left: 5 + text: Spell Healing + color: #269e26 + font: verdana-11px-rounded + + SpellSourceBox + id: spellSource + anchors.top: spellList.top + anchors.left: spellList.right + margin-left: 80 + width: 125 + font: verdana-11px-rounded + + Label + id: whenSpell + anchors.left: spellList.right + anchors.verticalCenter: prev.verticalCenter + text: When + margin-left: 7 + font: verdana-11px-rounded + + Label + id: isSpell + anchors.left: spellList.right + anchors.top: whenSpell.bottom + text: Is + margin-top: 9 + margin-left: 7 + font: verdana-11px-rounded + + SpellConditionBox + id: spellCondition + anchors.left: spellSource.left + anchors.top: spellSource.bottom + marin-top: 15 + width: 80 + font: verdana-11px-rounded + + TextEdit + id: spellValue + anchors.left: spellCondition.right + anchors.top: spellCondition.top + anchors.bottom: spellCondition.bottom + anchors.right: spellSource.right + font: verdana-11px-rounded + + Label + id: castSpell + anchors.left: isSpell.left + anchors.top: isSpell.bottom + text: Cast + margin-top: 9 + font: verdana-11px-rounded + + TextEdit + id: spellFormula + anchors.left: spellCondition.left + anchors.top: spellCondition.bottom + anchors.right: spellValue.right + font: verdana-11px-rounded + + Label + id: manaSpell + anchors.left: castSpell.left + anchors.top: castSpell.bottom + text: Mana Cost: + margin-top: 8 + font: verdana-11px-rounded + + TextEdit + id: manaCost + anchors.left: spellFormula.left + anchors.top: spellFormula.bottom + width: 40 + font: verdana-11px-rounded + + TextList + id: spellList + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.top: parent.top + padding: 1 + padding-top: 2 + width: 270 + margin-bottom: 7 + margin-left: 7 + margin-top: 10 + vertical-scrollbar: spellListScrollBar + + VerticalScrollBar + id: spellListScrollBar + anchors.top: spellList.top + anchors.bottom: spellList.bottom + anchors.right: spellList.right + step: 14 + pixels-scroll: true + + Button + id: addSpell + anchors.right: spellFormula.right + anchors.bottom: spellList.bottom + text: Add + size: 40 17 + font: cipsoftFont + + Button + id: MoveUp + anchors.right: prev.left + anchors.bottom: prev.bottom + margin-right: 5 + text: Move Up + size: 55 17 + font: cipsoftFont + + Button + id: MoveDown + anchors.right: prev.left + anchors.bottom: prev.bottom + margin-right: 5 + text: Move Down + size: 55 17 + font: cipsoftFont + +ItemHealing < FlatPanel + size: 490 120 + + Label + id: title + anchors.verticalCenter: parent.top + anchors.left: parent.left + margin-left: 5 + text: Item Healing + color: #ff4513 + font: verdana-11px-rounded + + SpellSourceBox + id: itemSource + anchors.top: itemList.top + anchors.right: parent.right + margin-right: 10 + width: 128 + font: verdana-11px-rounded + + Label + id: whenItem + anchors.left: itemList.right + anchors.verticalCenter: prev.verticalCenter + text: When + margin-left: 7 + font: verdana-11px-rounded + + Label + id: isItem + anchors.left: itemList.right + anchors.top: whenItem.bottom + text: Is + margin-top: 9 + margin-left: 7 + font: verdana-11px-rounded + + SpellConditionBox + id: itemCondition + anchors.left: itemSource.left + anchors.top: itemSource.bottom + marin-top: 15 + width: 80 + font: verdana-11px-rounded + + TextEdit + id: itemValue + anchors.left: itemCondition.right + anchors.top: itemCondition.top + anchors.bottom: itemCondition.bottom + width: 49 + font: verdana-11px-rounded + + Label + id: useItem + anchors.left: isItem.left + anchors.top: isItem.bottom + text: Use + margin-top: 15 + font: verdana-11px-rounded + + BotItem + id: itemId + anchors.left: itemCondition.left + anchors.top: itemCondition.bottom + + TextList + id: itemList + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.top: parent.top + padding: 1 + padding-top: 2 + width: 270 + margin-top: 10 + margin-bottom: 7 + margin-left: 8 + vertical-scrollbar: itemListScrollBar + + VerticalScrollBar + id: itemListScrollBar + anchors.top: itemList.top + anchors.bottom: itemList.bottom + anchors.right: itemList.right + step: 14 + pixels-scroll: true + + Button + id: addItem + anchors.right: itemValue.right + anchors.bottom: itemList.bottom + text: Add + size: 40 17 + font: cipsoftFont + + Button + id: MoveUp + anchors.right: prev.left + anchors.bottom: prev.bottom + margin-right: 5 + text: Move Up + size: 55 17 + font: cipsoftFont + + Button + id: MoveDown + anchors.right: prev.left + anchors.bottom: prev.bottom + margin-right: 5 + text: Move Down + size: 55 17 + font: cipsoftFont + +HealerPanel < Panel + size: 510 275 + + SpellHealing + id: spells + anchors.top: parent.top + margin-top: 8 + anchors.left: parent.left + + ItemHealing + id: items + anchors.top: prev.bottom + anchors.left: parent.left + margin-top: 10 + +HealBotSettingsPanel < Panel + size: 500 267 + padding-top: 8 + + FlatPanel + id: list + anchors.fill: parent + margin-right: 240 + padding-left: 6 + padding-right: 6 + padding-top: 6 + layout: + type: verticalBox + + Label + text: Additional Settings + text-align: center + font: verdana-11px-rounded + + HorizontalSeparator + + SettingCheckBox + id: Cooldown + text: Check spell cooldowns + margin-top: 10 + + SettingCheckBox + id: Visible + text: Items must be visible (recommended) + + SettingCheckBox + id: Delay + text: Don't use items when interacting + + SettingCheckBox + id: Interval + text: Additional delay when looting corpses + + SettingCheckBox + id: Conditions + text: Also check conditions from RL Tibia + + SettingCheckBox + id: MessageDelay + text: Cooldown based on "Aaaah..." message + + VerticalSeparator + anchors.top: prev.top + anchors.bottom: prev.bottom + anchors.left: prev.right + margin-left: 8 + + FlatPanel + id: profiles + anchors.fill: parent + anchors.left: prev.left + margin-left: 8 + margin-right: 8 + padding: 8 + + Label + text: Profile Settings + text-align: center + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + font: verdana-11px-rounded + + HorizontalSeparator + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + + Label + anchors.top: prev.bottom + margin-top: 30 + anchors.left: parent.left + anchors.right: parent.right + text-align: center + font: verdana-11px-rounded + text: Profile Name: + + TextEdit + id: Name + anchors.top: prev.bottom + margin-top: 3 + anchors.left: parent.left + anchors.right: parent.right + + Button + id: ResetSettings + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + text: Reset Current Profile + text-auto-resize: true + color: #ff4513 + +HealWindow < MainWindow + !text: tr('Self Healer') + size: 520 360 + @onEscape: self:hide() + + Label + id: title + anchors.left: parent.left + anchors.top: parent.top + margin-left: 2 + !text: tr('More important methods come first (Example: Exura gran above Exura)') + text-align: left + font: verdana-11px-rounded + color: #aeaeae + + HealerPanel + id: healer + anchors.top: prev.bottom + anchors.left: parent.left + + HealBotSettingsPanel + id: settings + anchors.top: title.bottom + anchors.left: parent.left + visible: false + + HorizontalSeparator + id: separator + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: closeButton.top + margin-bottom: 8 + + Button + id: closeButton + !text: tr('Close') + font: cipsoftFont + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 45 21 + margin-right: 5 + + Button + id: settingsButton + !text: tr('Settings') + font: cipsoftFont + anchors.left: parent.left + anchors.bottom: parent.bottom + size: 45 21 \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/Sio.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/Sio.lua new file mode 100644 index 0000000..ade2c0d --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/Sio.lua @@ -0,0 +1,252 @@ +setDefaultTab("Main") + local panelName = "advancedFriendHealer" + local ui = setupUI([[ +Panel + height: 19 + + BotSwitch + id: title + anchors.top: parent.top + anchors.left: parent.left + text-align: center + width: 130 + !text: tr('Friend Healer') + + Button + id: editList + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 3 + height: 17 + text: Setup + + ]], parent) + ui:setId(panelName) + + if not storage[panelName] then + storage[panelName] = { + minMana = 60, + minFriendHp = 40, + customSpellName = "exura max sio", + customSpell = false, + distance = 8, + itemHeal = false, + id = 3160, + exuraSio = false, + exuraGranSio = false, + exuraMasRes = false, + healEk = false, + healRp = false, + healEd = false, + healMs = false + } + end + + local config = storage[panelName] + + -- basic elements + ui.title:setOn(config.enabled) + ui.title.onClick = function(widget) + config.enabled = not config.enabled + widget:setOn(config.enabled) + end + ui.editList.onClick = function(widget) + sioListWindow:show() + sioListWindow:raise() + sioListWindow:focus() + end + + rootWidget = g_ui.getRootWidget() + if rootWidget then + sioListWindow = UI.createWindow('SioListWindow', rootWidget) + sioListWindow:hide() + + -- TextWindow + sioListWindow.spellName:setText(config.customSpellName) + sioListWindow.spellName.onTextChange = function(widget, text) + config.customSpellName = text + end + + -- botswitches + sioListWindow.spell:setOn(config.customSpell) + sioListWindow.spell.onClick = function(widget) + config.customSpell = not config.customSpell + widget:setOn(config.customSpell) + end + sioListWindow.item:setOn(config.itemHeal) + sioListWindow.item.onClick = function(widget) + config.itemHeal = not config.itemHeal + widget:setOn(config.itemHeal) + end + sioListWindow.exuraSio:setOn(config.exuraSio) + sioListWindow.exuraSio.onClick = function(widget) + config.exuraSio = not config.exuraSio + widget:setOn(config.exuraSio) + end + sioListWindow.exuraGranSio:setOn(config.exuraGranSio) + sioListWindow.exuraGranSio.onClick = function(widget) + config.exuraGranSio = not config.exuraGranSio + widget:setOn(config.exuraGranSio) + end + sioListWindow.exuraMasRes:setOn(config.exuraMasRes) + sioListWindow.exuraMasRes.onClick = function(widget) + config.exuraMasRes = not config.exuraMasRes + widget:setOn(config.exuraMasRes) + end + sioListWindow.vocation.ED:setOn(config.healEd) + sioListWindow.vocation.ED.onClick = function(widget) + config.healEd = not config.healEd + widget:setOn(config.healEd) + end + sioListWindow.vocation.MS:setOn(config.healMs) + sioListWindow.vocation.MS.onClick = function(widget) + config.healMs = not config.healMs + widget:setOn(config.healMs) + end + sioListWindow.vocation.EK:setOn(config.healEk) + sioListWindow.vocation.EK.onClick = function(widget) + config.healEk = not config.healEk + widget:setOn(config.healEk) + end + sioListWindow.vocation.RP:setOn(config.healRp) + sioListWindow.vocation.RP.onClick = function(widget) + config.healRp = not config.healRp + widget:setOn(config.healRp) + end + + -- functions + local updateMinManaText = function() + sioListWindow.manaInfo:setText("Minimum Mana >= " .. config.minMana .. "%") + end + local updateFriendHpText = function() + sioListWindow.friendHp:setText("Heal Friend Below " .. config.minFriendHp .. "% hp") + end + local updateDistanceText = function() + sioListWindow.distText:setText("Max Distance: " .. config.distance) + end + + -- scrollbars and text updates + sioListWindow.Distance:setValue(config.distance) + sioListWindow.Distance.onValueChange = function(scroll, value) + config.distance = value + updateDistanceText() + end + updateDistanceText() + + sioListWindow.minMana:setValue(config.minMana) + sioListWindow.minMana.onValueChange = function(scroll, value) + config.minMana = value + updateMinManaText() + end + updateMinManaText() + + sioListWindow.minFriendHp:setValue(config.minFriendHp) + sioListWindow.minFriendHp.onValueChange = function(scroll, value) + config.minFriendHp = value + updateFriendHpText() + end + updateFriendHpText() + + sioListWindow.itemId:setItemId(config.id) + sioListWindow.itemId.onItemChange = function(widget) + config.id = widget:getItemId() + end + + sioListWindow.closeButton.onClick = function(widget) + sioListWindow:hide() + end + + end + + -- local variables + local newTibia = g_game.getClientVersion() >= 960 + + local function isValid(name) + if not newTibia then return true end + + local voc = vBot.BotServerMembers[name] + if not voc then return true end + + if voc == 11 then voc = 1 + elseif voc == 12 then voc = 2 + elseif voc == 13 then voc = 3 + elseif voc == 14 then voc = 4 + end + + local isOk = false + if voc == 1 and config.healEk then + isOk = true + elseif voc == 2 and config.healRp then + isOk = true + elseif voc == 3 and config.healMs then + isOk = true + elseif voc == 4 and config.healEd then + isOk = true + end + + return isOk + end + + macro(200, function() + if not config.enabled then return end + if modules.game_cooldown.isGroupCooldownIconActive(2) then return end + + --[[ + 1. custom spell + 2. exura gran sio - at 50% of minHpValue + 3. exura gran mas res + 4. exura sio + 5. item healing + --]] + + -- exura gran sio & custom spell + if config.customSpell or config.exuraGranSio then + for i, spec in ipairs(getSpectators()) do + if spec:isPlayer() and spec ~= player and isValid(spec:getName()) and spec:canShoot() then + if isFriend(spec) then + if config.customSpell and spec:getHealthPercent() <= config.minFriendHp then + return cast(config.customSpellName .. ' "' .. spec:getName() .. '"', 1000) + end + if config.exuraGranSio and spec:getHealthPercent() <= config.minFriendHp/3 then + if canCast('exura gran sio "' .. spec:getName() ..'"') then + return cast('exura gran sio "' .. spec:getName() ..'"', 60000) + end + end + end + end + end + end + + -- exura gran mas res and standard sio + local friends = 0 + if config.exuraMasRes then + for i, spec in ipairs(getSpectators(player, largeRuneArea)) do + if spec:isPlayer() and spec ~= player and isValid(spec:getName()) and spec:canShoot() then + if isFriend(spec) and spec:getHealthPercent() <= config.minFriendHp then + friends = friends + 1 + end + end + end + if friends > 1 then + return cast('exura gran mas res', 2000) + end + end + if config.exuraSio or config.itemHeal then + for i, spec in ipairs(getSpectators()) do + if spec:isPlayer() and spec ~= player and isValid(spec:getName()) and spec:canShoot() then + if isFriend(spec) then + if spec:getHealthPercent() <= config.minFriendHp then + if config.exuraSio then + return cast('exura sio "' .. spec:getName() .. '"', 1000) + elseif findItem(config.id) and distanceFromPlayer(spec:getPosition()) <= config.distance then + return useWith(config.id, spec) + end + end + end + end + end + end + + end) +addSeparator() \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/alarms.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/alarms.lua new file mode 100644 index 0000000..ad58f03 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/alarms.lua @@ -0,0 +1,272 @@ +local panelName = "alarms" +local ui = setupUI([[ +Panel + height: 19 + + BotSwitch + id: title + anchors.top: parent.top + anchors.left: parent.left + text-align: center + width: 130 + !text: tr('Alarms') + + Button + id: alerts + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 3 + height: 17 + text: Edit + +]]) +ui:setId(panelName) + +if not storage[panelName] then + storage[panelName] = { + enabled = false, + playerAttack = false, + playerDetected = false, + playerDetectedLogout = false, + creatureDetected = false, + healthBelow = false, + healthValue = 40, + manaBelow = false, + manaValue = 50, + privateMessage = false, + ignoreFriends = true, + warnBoss = false, + bossName = '[B]' +} +end + + + +local config = storage[panelName] + +ui.title:setOn(config.enabled) +ui.title.onClick = function(widget) +config.enabled = not config.enabled +widget:setOn(config.enabled) +end + +-- new var's validation +config.messageText = config.messageText or "" +config.bossName = config.bossName or "" + +rootWidget = g_ui.getRootWidget() +if rootWidget then + alarmsWindow = UI.createWindow('AlarmsWindow', rootWidget) + alarmsWindow:hide() + + alarmsWindow.closeButton.onClick = function(widget) + alarmsWindow:hide() + end + + alarmsWindow.playerAttack:setOn(config.playerAttack) + alarmsWindow.playerAttack.onClick = function(widget) + config.playerAttack = not config.playerAttack + widget:setOn(config.playerAttack) + end + + alarmsWindow.playerDetected:setOn(config.playerDetected) + alarmsWindow.playerDetected.onClick = function(widget) + config.playerDetected = not config.playerDetected + widget:setOn(config.playerDetected) + end + + alarmsWindow.playerDetectedLogout:setChecked(config.playerDetectedLogout) + alarmsWindow.playerDetectedLogout.onClick = function(widget) + config.playerDetectedLogout = not config.playerDetectedLogout + widget:setChecked(config.playerDetectedLogout) + end + + alarmsWindow.creatureDetected:setOn(config.creatureDetected) + alarmsWindow.creatureDetected.onClick = function(widget) + config.creatureDetected = not config.creatureDetected + widget:setOn(config.creatureDetected) + end + + alarmsWindow.healthBelow:setOn(config.healthBelow) + alarmsWindow.healthBelow.onClick = function(widget) + config.healthBelow = not config.healthBelow + widget:setOn(config.healthBelow) + end + + alarmsWindow.healthValue.onValueChange = function(scroll, value) + config.healthValue = value + alarmsWindow.healthBelow:setText("Health < " .. config.healthValue .. "%") + end + alarmsWindow.healthValue:setValue(config.healthValue) + + alarmsWindow.manaBelow:setOn(config.manaBelow) + alarmsWindow.manaBelow.onClick = function(widget) + config.manaBelow = not config.manaBelow + widget:setOn(config.manaBelow) + end + + alarmsWindow.manaValue.onValueChange = function(scroll, value) + config.manaValue = value + alarmsWindow.manaBelow:setText("Mana < " .. config.manaValue .. "%") + end + alarmsWindow.manaValue:setValue(config.manaValue) + + alarmsWindow.privateMessage:setOn(config.privateMessage) + alarmsWindow.privateMessage.onClick = function(widget) + config.privateMessage = not config.privateMessage + widget:setOn(config.privateMessage) + end + + alarmsWindow.ignoreFriends:setOn(config.ignoreFriends) + alarmsWindow.ignoreFriends.onClick = function(widget) + config.ignoreFriends = not config.ignoreFriends + widget:setOn(config.ignoreFriends) + end + + alarmsWindow.warnBoss:setOn(config.warnBoss) + alarmsWindow.warnBoss.onClick = function(widget) + config.warnBoss = not config.warnBoss + widget:setOn(config.warnBoss) + end + + alarmsWindow.bossName:setText(config.bossName) + alarmsWindow.bossName.onTextChange = function(widget, text) + config.bossName = text + end + + alarmsWindow.warnMessage:setOn(config.warnMessage) + alarmsWindow.warnMessage.onClick = function(widget) + config.warnMessage = not config.warnMessage + widget:setOn(config.warnMessage) + end + + alarmsWindow.messageText:setText(config.messageText) + alarmsWindow.messageText.onTextChange = function(widget, text) + config.messageText = text + end + + local pName = player:getName() + onTextMessage(function(mode, text) + if config.enabled and config.playerAttack and string.match(text, "hitpoints due to an attack") and not string.match(text, "hitpoints due to an attack by a ") then + playSound("/sounds/Player_Attack.ogg") + g_window.setTitle(pName .. " - Player Attacks!") + return + end + + if config.warnMessage and config.messageText:len() > 0 then + text = text:lower() + local parts = string.split(config.messageText, ",") + for i=1,#parts do + local part = parts[i] + part = part:trim() + part = part:lower() + + if text:find(part) then + delay(1500) + playSound(g_resources.fileExists("/sounds/Special_Message.ogg") and "/sounds/Special_Message.ogg" or "/sounds/Private_Message.ogg") + g_window.setTitle(pName .. " - Special Message Detected: "..part) + return + end + end + end + end) + + macro(100, function() + if not config.enabled then + return + end + local specs = getSpectators() + if config.playerDetected then + for _, spec in ipairs(specs) do + if spec:isPlayer() and spec:getName() ~= name() then + local specPos = spec:getPosition() + if (not config.ignoreFriends or not isFriend(spec)) and math.max(math.abs(posx()-specPos.x), math.abs(posy()-specPos.y)) <= 8 then + playSound("/sounds/Player_Detected.ogg") + delay(1500) + g_window.setTitle(pName .. " - Player Detected! "..spec:getName()) + if config.playerDetectedLogout then + modules.game_interface.tryLogout(false) + end + return + end + end + end + end + + if config.creatureDetected then + for _, spec in ipairs(specs) do + if not spec:isPlayer() then + local specPos = spec:getPosition() + if math.max(math.abs(posx()-specPos.x), math.abs(posy()-specPos.y)) <= 8 then + playSound("/sounds/Creature_Detected.ogg") + delay(1500) + g_window.setTitle(pName .. " - Creature Detected! "..spec:getName()) + return + end + end + end + end + + if config.warnBoss then + -- experimental, but since we check only names i think the best way would be to combine all spec's names into one string and then check it to avoid multiple loops + if config.bossName:len() > 0 then + local names = string.split(config.bossName, ",") + local combinedString = "" + for _, spec in ipairs(specs) do + local specPos = spec:getPosition() + if math.max(math.abs(posx() - specPos.x), math.abs(posy() - specPos.y)) <= 8 then + local name = spec:getName():lower() + -- add special sign between names to avoid unwanted combining mistakes + combinedString = combinedString .."&"..name + end + end + for i=1,#names do + local name = names[i] + name = name:trim() + name = name:lower() + + if combinedString:find(name) then + playSound(g_resources.fileExists("/sounds/Special_Creature.ogg") and "/sounds/Special_Creature.ogg" or "/sounds/Creature_Detected.ogg") + delay(1500) + g_window.setTitle(pName .. " - Special Creature Detected: "..name) + return + end + + end + end + end + + if config.healthBelow then + if hppercent() <= config.healthValue then + playSound("/sounds/Low_Health.ogg") + delay(1500) + g_window.setTitle(pName .. " - Low Health! only: "..hppercent().."%") + return + end + end + + if config.manaBelow then + if manapercent() <= config.manaValue then + playSound("/sounds/Low_Mana.ogg") + delay(1500) + g_window.setTitle(pName .. " - Low Mana! only: "..manapercent().."%") + return + end + end + end) + + onTalk(function(name, level, mode, text, channelId, pos) + if mode == 4 and config.enabled and config.privateMessage then + playSound("/sounds/Private_Message.ogg") + g_window.setTitle(pName .. " - Private Message from: " .. name) + return + end + end) +end + +ui.alerts.onClick = function(widget) + alarmsWindow:show() + alarmsWindow:raise() + alarmsWindow:focus() +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/alarms.otui b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/alarms.otui new file mode 100644 index 0000000..c530efc --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/alarms.otui @@ -0,0 +1,181 @@ +AlarmsWindow < MainWindow + !text: tr('Alarms') + size: 300 280 + @onEscape: self:hide() + + BotSwitch + id: playerAttack + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-align: center + text: Player Attack + !tooltip: tr('Alerts when attacked by player.') + + BotSwitch + id: playerDetected + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-top: 4 + text-align: center + text: Player Detected + !tooltip: tr('Alerts when a player is detected on screen.') + + CheckBox + id: playerDetectedLogout + anchors.top: playerDetected.top + anchors.left: parent.horizontalCenter + anchors.right: parent.right + margin-top: 3 + margin-left: 4 + text: Logout + !tooltip: tr('Attempts to logout when a player is detected on screen.') + + BotSwitch + id: ignoreFriends + anchors.left: parent.left + anchors.top: playerDetected.bottom + anchors.right: parent.right + text-align: center + margin-top: 4 + text: Ignore Friends + !tooltip: tr('Player detection alerts will ignore friends.') + + HorizontalSeparator + id: sepPlayer + anchors.right: parent.right + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 4 + + BotSwitch + id: creatureDetected + anchors.left: parent.left + anchors.right: parent.right + anchors.top: sepPlayer.bottom + margin-top: 4 + text-align: center + text: Creature Detected + !tooltip: tr('Alerts when a creature is detected on screen.') + + BotSwitch + id: warnBoss + anchors.left: parent.left + anchors.top: prev.bottom + anchors.right: parent.horizontalCenter + text-align: center + margin-top: 5 + text: Creature Name + !tooltip: tr('Alerts when a creature/npc with name is detected on screen. \n eg: Benjamin or [boss] would detect a creature with [boss] in name. \n You can add many examples, just separate them by comma.') + + BotTextEdit + id: bossName + anchors.left: prev.right + margin-left: 4 + anchors.top: prev.top + anchors.right: parent.right + margin-top: 1 + height: 17 + font: terminus-10px + + HorizontalSeparator + id: sepCreature + anchors.right: parent.right + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 4 + + BotSwitch + id: healthBelow + anchors.left: parent.left + anchors.top: prev.bottom + anchors.right: parent.horizontalCenter + text-align: center + margin-top: 4 + text: Health < 50% + + HorizontalScrollBar + id: healthValue + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: healthBelow.top + margin-left: 3 + margin-top: 2 + minimum: 1 + maximum: 100 + step: 1 + + BotSwitch + id: manaBelow + anchors.left: parent.left + anchors.top: healthBelow.bottom + anchors.right: parent.horizontalCenter + text-align: center + margin-top: 4 + text: Mana < 50% + + HorizontalScrollBar + id: manaValue + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: manaBelow.top + margin-left: 3 + margin-top: 2 + minimum: 1 + maximum: 100 + step: 1 + + HorizontalSeparator + id: sepMessages + anchors.right: parent.right + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 4 + + BotSwitch + id: privateMessage + anchors.left: parent.left + anchors.top: prev.bottom + anchors.right: parent.right + text-align: center + margin-top: 4 + text: Private Message + !tooltip: tr('Alerts when recieving a private message.') + + BotSwitch + id: warnMessage + anchors.left: parent.left + anchors.top: prev.bottom + anchors.right: parent.horizontalCenter + text-align: center + margin-top: 5 + text: Message Alert + !tooltip: tr('Alerts when players receive a message that contains given part. \n Eg. event - will trigger alert whenever a message with word event appears. \n You can give many examples, just separate them by comma.') + + BotTextEdit + id: messageText + anchors.left: prev.right + margin-left: 4 + anchors.top: prev.top + anchors.right: parent.right + margin-top: 1 + height: 17 + font: terminus-10px + + HorizontalSeparator + id: separator + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: closeButton.top + margin-bottom: 8 + + Button + id: closeButton + !text: tr('Close') + font: cipsoftFont + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 45 21 + margin-top: 15 + margin-right: 5 \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/analyzer.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/analyzer.lua new file mode 100644 index 0000000..615f3f0 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/analyzer.lua @@ -0,0 +1,1579 @@ +--[[ + Bot-based Tibia 12 features v1.0 + made by Vithrax + + Credits also to: + - Martín#2318 + - Lee#7725 + + Thanks for ideas, graphics, functions, design tips! + + br, Vithrax +]] + + +local lootWorth = 0 +local wasteWorth = 0 +local balance = 0 +local balanceDesc = "" +local hourDesc = "" +local desc = "" +local hour = "" +local launchTime = now +local startExp = exp() +local dmgTable = {} +local healTable = {} +local expTable = {} +local totalDmg = 0 +local totalHeal = 0 +local dmgDistribution = {} +local first = {l="-", r="0"} +local second = {l="-", r="0"} +local third = {l="-", r="0"} +local fourth = {l="-", r="0"} +local five = {l="-", r="0"} +storage.bestHit = storage.bestHit or 0 +storage.bestHeal = storage.bestHeal or 0 +local lootedItems = {} +local useData = {} +local usedItems ={} +local lastDataSend = {0, 0} +local analyzerButton +local killList = {} +local membersData = {} +HuntingSessionStart = os.date('%Y-%m-%d, %H:%M:%S') + +if not storage.analyzers then + storage.analyzers = { + trackedLoot = {}, + customPrices = {}, + lootChannel = true, + rarityFrames = true + } +end + +storage.analyzers = storage.analyzers or {} +storage.analyzers.trackedLoot = storage.analyzers.trackedLoot or {} + +local trackedLoot = storage.analyzers.trackedLoot + +--destroy old windows +local windowsTable = {"MainAnalyzerWindow", "HuntingAnalyzerWindow", "LootAnalyzerWindow", "SupplyAnalyzerWindow", "ImpactAnalyzerWindow", "XPAnalyzerWindow", "PartyAnalyzerWindow", "DropTracker"} +for i, window in ipairs(windowsTable) do + local element = g_ui.getRootWidget():recursiveGetChildById(window) + + if element then + element:destroy() + end +end + +local mainWindow = UI.createMiniWindow("MainAnalyzerWindow") +mainWindow:hide() +mainWindow:setContentMaximumHeight(220) +local huntingWindow = UI.createMiniWindow("HuntingAnalyzer") +huntingWindow:hide() +local lootWindow = UI.createMiniWindow("LootAnalyzer") +lootWindow:hide() +local supplyWindow = UI.createMiniWindow("SupplyAnalyzer") +supplyWindow:hide() +local impactWindow = UI.createMiniWindow("ImpactAnalyzer") +impactWindow:hide() +impactWindow:setContentMaximumHeight(615) +local xpWindow = UI.createMiniWindow("XPAnalyzer") +xpWindow:hide() +xpWindow:setContentMaximumHeight(230) +local settingsWindow = UI.createWindow("FeaturesWindow") +settingsWindow:hide() +local partyHuntWindow = UI.createMiniWindow("PartyAnalyzerWindow") +partyHuntWindow:hide() +local dropTrackerWindow = UI.createMiniWindow("DropTracker") +dropTrackerWindow:hide() + +--f +local toggle = function() + if mainWindow:isVisible() then + analyzerButton:setOn(false) + mainWindow:close() + else + analyzerButton:setOn(true) + mainWindow:open() + end +end + +local drawGraph = function(graph, value) + graph:addValue(value) +end + +local toggleAnalyzer = function(window) + if window:isVisible() then + window:hide() + else + window:show() + end +end + +local function getSumStats() + local totalWaste = 0 + local totalLoot = 0 + + for k,v in pairs(membersData) do + totalWaste = totalWaste + v.waste + totalLoot = totalLoot + v.loot + end + + local totalBalance = totalLoot - totalWaste + + return totalWaste, totalLoot, totalBalance +end + +local function clipboardData() + local totalWaste, totalLoot, totalBalance = getSumStats() + local final = "" + + + local first = "Session data: From " .. HuntingSessionStart .." to ".. os.date('%Y-%m-%d, %H:%M:%S') + local second = "Session: " .. sessionTime() + local third = "Loot Type: Market" + local fourth = "Loot " .. format_thousand(totalLoot, true) + local fifth = "Supplies " .. format_thousand(totalWaste, true) + local six = "Balance " .. format_thousand(totalBalance, true) + + local t = {first, second, third, fourth, fifth, six} + for i, string in ipairs(t) do + final = final.. "\n"..string + end + + --user data now + for k,v in pairs(membersData) do + final = final.. "\n".. k + + final = final.. "\n\tLoot "..v.loot + final = final.. "\n\tSupplies "..v.waste + final = final.. "\n\tBalance "..v.balance + final = final.. "\n\tDamage "..v.damage + final = final.. "\n\tHealing "..v.heal + end + + g_window.setClipboardText(final) +end + +-- create analyzers button +analyzerButton = modules.game_buttons.buttonsWindow.contentsPanel and modules.game_buttons.buttonsWindow.contentsPanel.buttons.botAnalyzersButton +analyzerButton = analyzerButton or modules.client_topmenu.getButton("botAnalyzersButton") +if analyzerButton then + analyzerButton:destroy() +end + +--button +analyzerButton = modules.client_topmenu.addRightGameToggleButton('botAnalyzersButton', 'vBot Analyzers', '/images/topbuttons/analyzers', toggle, false, 999999) +analyzerButton:setOn(false) + +--toggles window +mainWindow.contentsPanel.HuntingAnalyzer.onClick = function() + toggleAnalyzer(huntingWindow) +end +mainWindow.onClose = function() + analyzerButton:setOn(false) +end +mainWindow.contentsPanel.LootAnalyzer.onClick = function() + toggleAnalyzer(lootWindow) +end +mainWindow.contentsPanel.SupplyAnalyzer.onClick = function() + toggleAnalyzer(supplyWindow) +end +mainWindow.contentsPanel.ImpactAnalyzer.onClick = function() + toggleAnalyzer(impactWindow) +end +mainWindow.contentsPanel.XPAnalyzer.onClick = function() + toggleAnalyzer(xpWindow) +end +mainWindow.contentsPanel.PartyHunt.onClick = function() + toggleAnalyzer(partyHuntWindow) +end +mainWindow.contentsPanel.DropTracker.onClick = function() + toggleAnalyzer(dropTrackerWindow) +end + +--hunting +local sessionTimeLabel = UI.DualLabel("Session:", "00:00h", {}, huntingWindow.contentsPanel).right +local xpGainLabel = UI.DualLabel("XP Gain:", "0", {}, huntingWindow.contentsPanel).right +local xpHourLabel = UI.DualLabel("XP/h:", "0", {}, huntingWindow.contentsPanel).right +local lootLabel = UI.DualLabel("Loot:", "0", {}, huntingWindow.contentsPanel).right +local suppliesLabel = UI.DualLabel("Supplies:", "0", {}, huntingWindow.contentsPanel).right +local balanceLabel = UI.DualLabel("Balance:", "0", {}, huntingWindow.contentsPanel).right +local damageLabel = UI.DualLabel("Damage:", "0", {}, huntingWindow.contentsPanel).right +local damageHourLabel = UI.DualLabel("Damage/h:", "0", {}, huntingWindow.contentsPanel).right +local healingLabel = UI.DualLabel("Healing:", "0", {}, huntingWindow.contentsPanel).right +local healingHourLabel = UI.DualLabel("Healing/h:", "0", {}, huntingWindow.contentsPanel).right +UI.DualLabel("Killed Monsters:", "", {maxWidth = 200}, huntingWindow.contentsPanel) +local killedList = UI.createWidget("AnalyzerListPanel", huntingWindow.contentsPanel) +UI.DualLabel("Looted items:", "", {maxWidth = 200}, huntingWindow.contentsPanel) +local lootList = UI.createWidget("AnalyzerListPanel", huntingWindow.contentsPanel) + + +--party +UI.Button("Copy to Clipboard", function() clipboardData() end, partyHuntWindow.contentsPanel) +UI.Button("Reset Sessions", function() + if BotServer._websocket then + BotServer.send("partyHunt", false) + end +end, partyHuntWindow.contentsPanel) + +local switch = addSwitch("sendData", "Send Analyzer Data", function(widget) + widget:setOn(not widget:isOn()) + storage.sendPartyAnalyzerData = widget:isOn() +end, partyHuntWindow.contentsPanel) +switch:setOn(storage.sendPartyAnalyzerData) +UI.Separator(partyHuntWindow.contentsPanel) +local partySessionTimeLabel = UI.DualLabel("Session:", "00:00h", {}, partyHuntWindow.contentsPanel).right +local partyLootLabel = UI.DualLabel("Loot:", "0", {}, partyHuntWindow.contentsPanel).right +local partySuppliesLabel = UI.DualLabel("Supplies:", "0", {}, partyHuntWindow.contentsPanel).right +local partyBalanceLabel = UI.DualLabel("Balance:", "0", {}, partyHuntWindow.contentsPanel).right +UI.Separator(partyHuntWindow.contentsPanel) + +local function maintainDropTable() + local panel = dropTrackerWindow.contentsPanel + + for k,v in pairs(trackedLoot) do + local widget = panel[k] + if not widget then + trackedLoot[k] = nil + end + end +end + +local function createTrackedItems() + local panel = dropTrackerWindow.contentsPanel + + for i, child in ipairs(panel:getChildren()) do + if i > 2 then + child:destroy() + end + end + + for k,v in pairs(trackedLoot) do + local dropLoot = UI.createWidget("TrackerItem", dropTrackerWindow.contentsPanel) + local item = dropLoot.item + local name = dropLoot.name + local drops = dropLoot.drops + local id = tonumber(k) + local itemName = id == 3031 and "gold coin" or id == 3035 and "platinum coin" or id == 3043 and "crystal coin" or Item.create(id):getMarketData().name + + dropLoot:setId(id) + item:setItemId(id) + if item:getItemCount() > 1 then + item:setItemCount(1) + end + name:setText(itemName) + drops:setText("Loot Drops: "..v) + + dropLoot.onDoubleClick = function() + local id = dropLoot.item:getItemId() + trackedLoot[tostring(id)] = 0 + drops:setText("Loot Drops: 0") + end + + for i, child in pairs(dropLoot:getChildren()) do + child:setTooltip("Double click to reset or clear item to remove.") + end + + item.onItemChange = function(widget) + local id = widget:getItemId() + if id == 0 then + trackedLoot[widget:getParent():getId()] = nil + if tonumber(widget:getParent():getId()) then + widget:getParent():destroy() + return + end + widget:setImageSource('/images/ui/item') + widget:getParent():setId("blank") + name:setText("Set Item to start track.") + drops:setText("Loot Drops: 0") + return + end + + -- only amount have changed, ignore + if tonumber(widget:getParent():getId()) == id then return end + local itemName = id == 3031 and "gold coin" or id == 3035 and "platinum coin" or id == 3043 and "crystal coin" or Item.create(id):getMarketData().name + + if trackedLoot[tostring(id)] then + warn("vBot[Drop Tracker]: Item already added!") + name:setText("Set Item to start track.") + widget:setItemId(0) + return + end + + widget:setImageSource('') + drops:setText("Loot Drops: 0") + name:setText(itemName) + trackedLoot[tostring(id)] = trackedLoot[tostring(id)] or 0 + widget:getParent():setId(id) + maintainDropTable() + end + end +end + +--drop tracker +UI.Button("Add item to track drops", function() + local dropLoot = UI.createWidget("TrackerItem", dropTrackerWindow.contentsPanel) + local item = dropLoot.item + local name = dropLoot.name + local drops = dropLoot.drops + + item:setImageSource('/images/ui/item') + + dropLoot.onDoubleClick = function() + local id = dropLoot.item:getItemId() + trackedLoot[tostring(id)] = 0 + drops:setText("Loot Drops: 0") + end + + for i, child in pairs(dropLoot:getChildren()) do + child:setTooltip("Double click to reset or clear item to remove.") + end + + item.onItemChange = function(widget) + local id = widget:getItemId() + + if id == 0 then + trackedLoot[widget:getParent():getId()] = nil + if tonumber(widget:getParent():getId()) then + widget:getParent():destroy() + return + end + widget:setImageSource('/images/ui/item') + widget:getParent():setId("blank") + name:setText("Set Item to start track.") + drops:setText("Loot Drops: 0") + return + end + + -- only amount have changed, ignore + if tonumber(widget:getParent():getId()) == id then return end + local itemName = id == 3031 and "gold coin" or id == 3035 and "platinum coin" or id == 3043 and "crystal coin" or Item.create(id):getMarketData().name + + if trackedLoot[tostring(id)] then + warn("vBot[Drop Tracker]: Item already added!") + name:setText("Set Item to start track.") + widget:setItemId(0) + return + end + + widget:setImageSource('') + drops:setText("Loot Drops: 0") + name:setText(itemName) + trackedLoot[tostring(id)] = trackedLoot[tostring(id)] or 0 + widget:getParent():setId(id) + maintainDropTable() + end +end, dropTrackerWindow.contentsPanel) + +UI.Separator(dropTrackerWindow.contentsPanel) +createTrackedItems() + + +--loot +local lootInLootAnalyzerLabel = UI.DualLabel("Gold Value:", "0", {}, lootWindow.contentsPanel).right +local lootHourInLootAnalyzerLabel = UI.DualLabel("Per Hour:", "0", {}, lootWindow.contentsPanel).right +UI.Separator(lootWindow.contentsPanel) +--//items panel +local lootItems = UI.createWidget("AnalyzerItemsPanel", lootWindow.contentsPanel) +UI.Separator(lootWindow.contentsPanel) +--//graph +local lootGraph = UI.createWidget("AnalyzerGraph", lootWindow.contentsPanel) + lootGraph:setTitle("Loot/h") + drawGraph(lootGraph, 0) + + + + +--supplies +local suppliesInSuppliesAnalyzerLabel = UI.DualLabel("Gold Value:", "0", {}, supplyWindow.contentsPanel).right +local suppliesHourInSuppliesAnalyzerLabel = UI.DualLabel("Per Hour:", "0", {}, supplyWindow.contentsPanel).right +UI.Separator(supplyWindow.contentsPanel) +--//items panel +local supplyItems = UI.createWidget("AnalyzerItemsPanel", supplyWindow.contentsPanel) +UI.Separator(supplyWindow.contentsPanel) +--//graph +local supplyGraph = UI.createWidget("AnalyzerGraph", supplyWindow.contentsPanel) + supplyGraph:setTitle("Waste/h") + drawGraph(supplyGraph, 0) + + + + +-- impact + +--- damage +local title = UI.DualLabel("Damage", "", {}, impactWindow.contentsPanel).left +title:setColor('#E3242B') +local totalDamageLabel = UI.DualLabel("Total:", "0", {}, impactWindow.contentsPanel).right +local maxDpsLabel = UI.DualLabel("Max-DPS:", "0", {}, impactWindow.contentsPanel).right +local bestHitLabel = UI.DualLabel("All-Time High:", "0", {}, impactWindow.contentsPanel).right +UI.Separator(impactWindow.contentsPanel) +local dmgGraph = UI.createWidget("AnalyzerGraph", impactWindow.contentsPanel) + dmgGraph:setTitle("DPS") + drawGraph(dmgGraph, 0) + + +--- distribution +UI.Separator(impactWindow.contentsPanel) +local title2 = UI.DualLabel("Damage Distribution", "", {maxWidth = 150}, impactWindow.contentsPanel).left +title2:setColor('#FABD02') +local top1 = UI.DualLabel("-", "0", {maxWidth = 200}, impactWindow.contentsPanel) +local top2 = UI.DualLabel("-", "0", {maxWidth = 200}, impactWindow.contentsPanel) +local top3 = UI.DualLabel("-", "0", {maxWidth = 200}, impactWindow.contentsPanel) +local top4 = UI.DualLabel("-", "0", {maxWidth = 200}, impactWindow.contentsPanel) +local top5 = UI.DualLabel("-", "0", {maxWidth = 200}, impactWindow.contentsPanel) + +top1.left:setWidth(135) +top2.left:setWidth(135) +top3.left:setWidth(135) +top4.left:setWidth(135) +top5.left:setWidth(135) + + +--- healing +UI.Separator(impactWindow.contentsPanel) +local title3 = UI.DualLabel("Healing", "", {}, impactWindow.contentsPanel).left +title3:setColor('#03C04A') +local totalHealingLabel = UI.DualLabel("Total:", "0", {}, impactWindow.contentsPanel).right +local maxHpsLabel = UI.DualLabel("Max-HPS:", "0", {}, impactWindow.contentsPanel).right +local bestHealLabel = UI.DualLabel("All-Time High:", "0", {}, impactWindow.contentsPanel).right +UI.Separator(impactWindow.contentsPanel) +--//graph +local healGraph = UI.createWidget("AnalyzerGraph", impactWindow.contentsPanel) + healGraph:setTitle("HPS") + drawGraph(healGraph, 0) + + + + + + + +--xp +local xpGrainInXpLabel = UI.DualLabel("XP Gain:", "0", {}, xpWindow.contentsPanel).right +local xpHourInXpLabel = UI.DualLabel("XP/h:", "0", {}, xpWindow.contentsPanel).right +local nextLevelLabel = UI.DualLabel("Next Level:", "-", {}, xpWindow.contentsPanel).right +local progressBar = UI.createWidget("AnalyzerProgressBar", xpWindow.contentsPanel) +progressBar:setPercent(modules.game_skills.skillsWindow.contentsPanel.level.percent:getPercent()) +UI.Separator(xpWindow.contentsPanel) +--//graph +local xpGraph = UI.createWidget("AnalyzerGraph", xpWindow.contentsPanel) + xpGraph:setTitle("XP/h") + drawGraph(xpGraph, 0) + + + +--############################################# +--############################################# UI DONE +--############################################# +--############################################# +--############################################# +--############################################# + +setDefaultTab("Main") +-- first, the variables + +local console = modules.game_console +local regex = [[ ([^,|^.]+)]] +local noData = {} +local data = {} + +local function getColor(v) + if v >= 10000000 then -- 10kk, red + return "#FF0000" + elseif v >= 5000000 then -- 5kk, orange + return "#FFA500" + elseif v >= 1000000 then -- 1kk, yellow + return "#FFFF00" + elseif v >= 100000 then -- 100k, purple + return "#F25AED" + elseif v >= 10000 then -- 10k, blue + return "#5F8DF7" + elseif v >= 1000 then -- 1k, green + return "#00FF00" + elseif v >= 50 then + return "#FFFFFF" -- 50gp, white + else + return "#aaaaaa" -- less than 100gp, grey + end +end + +local function formatStr(str) + if string.starts(str, "a ") then + str = str:sub(2, #str) + elseif string.starts(str, "an ") then + str = str:sub(3, #str) + end + + local n = getFirstNumberInText(str) + if n then + str = string.split(str, tostring(n))[1] + str = str:sub(1,#str-1) + end + + return str:trim() +end + +local function getPrice(name) + name = formatStr(name) + name = name:lower() + -- first check custom prices + if storage.analyzers.customPrices[name] then + return storage.analyzers.customPrices[name] + end + + -- if already checked and no data skip looping items.lua + if noData[name] then + return 0 + end + + -- maybe was already checked, if so, skip looping items.lua + if data[name] then + return data[name] + end + + -- searching in items.lua - big table, if possible skip + for k,v in pairs(LootItems) do + if name == k then + data[name] = v + return v + end + end + + -- if no data, save it and return 0 + noData[name] = true + return 0 +end + +local expGained = function() + return exp() - startExp +end + +function format_thousand(v, comma) + comma = comma and "," or "." + if not v then return 0 end + local s = string.format("%d", math.floor(v)) + local pos = string.len(s) % 3 + if pos == 0 then pos = 3 end + return string.sub(s, 1, pos) + .. string.gsub(string.sub(s, pos+1), "(...)", comma.."%1") +end + +local expLeft = function() + local level = lvl()+1 + return math.floor((50*level*level*level)/3 - 100*level*level + (850*level)/3 - 200) - exp() +end + +niceTimeFormat = function(v) -- v in seconds + local hours = string.format("%02.f", math.floor(v/3600)) + local mins = string.format("%02.f", math.floor(v/60 - (hours*60))) + return hours .. ":" .. mins .. "h" +end +local uptime +sessionTime = function() + uptime = math.floor((now - launchTime)/1000) + return niceTimeFormat(uptime) +end +sessionTime() + +local expPerHour = function(calculation) + local r = 0 + if #expTable > 0 then + r = exp() - expTable[1] + else + return "-" + end + + if uptime < 15*60 then + r = math.ceil((r/uptime)*60*60) + else + r = math.ceil(r*8) + end + if calculation then + return r + else + return format_thousand(r) + end +end + +local function add(t, text, color, last) + table.insert(t, text) + table.insert(t, color) + if not last then + table.insert(t, ", ") + table.insert(t, "#FFFFFF") + end +end + +-- Bot Server +local function sendData() + if BotServer._websocket then + local totalDmg, totalHeal, lootWorth, wasteWorth, balance = getHuntingData() + local outfit = player:getOutfit() + outfit.mount = 0 + local t = { + totalDmg, + totalHeal, + balance, + hppercent(), + manapercent(), + outfit, + player:isPartyLeader(), + lootWorth, + wasteWorth, + modules.game_skills.skillsWindow.contentsPanel.stamina.value:getText(), + format_thousand(expGained()), + expPerHour(), + balanceDesc .. " (" .. hourDesc .. ")", + sessionTime() + } + + -- validation + if lastDataSend.totalDmg ~= t[1] and lastDataSend.totalHeal ~= t[2] then + BotServer.send("partyHunt", t) + lastDataSend[1] = t[1] + lastDataSend[2] = t[2] + end + end +end + +-- process data +BotServer.listen("partyHunt", function(name, message) + if message == true then + sendData() + elseif message == false then + resetAnalyzerSessionData() + else + membersData[name] = { + damage = message[1], + heal = message[2], + balance = message[3], + hp = message[4], + mana = message[5], + outfit = message[6], + leader = message[7], + loot = message[8], + waste = message[9], + stamina = message[10], + expGained = message[11], + expH = message[12], + balanceH = message[13], + session = message[14] + } + + local widgetName = "Widget"..name + local widget = partyHuntWindow.contentsPanel[widgetName] or UI.createWidget("MemberWidget", partyHuntWindow.contentsPanel) + widget:setId(widgetName) + widget.lastUpdate = now + + + local t = membersData[name] + widget.name:setText(name) + widget.name:setColor("white") + if t.leader then + widget.name:setColor('#f8db38') + end + schedule(10*1000, function() + if widget and widget.lastUpdate and now - widget.lastUpdate > 10000 then + widget.name:setText(widget.name:getText().. " [inactive]") + widget.name:setColor("#aeaeae") + widget.health:setBackgroundColor("#aeaeae") + widget.mana:setBackgroundColor("#aeaeae") + widget.balance.value:setText("-") + widget.damage.value:setText("-") + widget.healing.value:setText("-") + widget.creature:disable() + end + end) + widget.creature:setOutfit(t.outfit) + widget.health:setPercent(t.hp) + widget.health:setBackgroundColor("#00c000") + widget.mana:setPercent(t.mana) + widget.mana:setBackgroundColor("#0000FF") + widget.balance.value:setText(format_thousand(t.balance)) + if t.balance < 0 then + widget.balance.value:setColor('#ff9854') + elseif t.balance > 0 then + widget.balance.value:setColor('#45ad25') + else + widget.balance.value:setColor('white') + end + widget.damage.value:setText(format_thousand(t.damage)) + widget.healing.value:setText(format_thousand(t.heal)) + + widget.onDoubleClick = function() + membersData[name] = nil + widget:destroy() + end + + --tooltip + local tooltip = "Session: "..t.session.."\n".. + "Stamina: "..t.stamina.."\n".. + "Exp Gained: "..t.expGained.."\n".. + "Exp per Hour: "..t.expH.."\n".. + "Balance: "..t.balanceH + + widget.creature:setTooltip(tooltip) + end +end) + + +function hightlightText(widget, color, duration) + for i=0,duration do + schedule(i * 250, function() + if i == duration or (i > 0 and i % 2 == 0) then + widget:setColor("#FFFFFF") + else + widget:setColor(color) + end + end) + end +end + +local nameRegex = [[Loot of (?:an |a |the |)([^:]+)]] +onTextMessage(function(mode, text) + if not storage.analyzers.lootChannel then return end + if not text:find("Loot of") and not text:find("The following items are available in your reward chest") then return end + local name + + -- adding monster to killed list + if text:find("Loot of") then + name = regexMatch(text, nameRegex)[1][2] + if not killList[name] then + killList[name] = 1 + else + killList[name] = killList[name] + 1 + end + refreshKills() + end + -- variables + local split = string.split(text, ":") + local re = regexMatch(split[2], regex) + local combinedWorth = 0 + local formatted + local div + local t = {} + local messageT = {} + + -- add timestamp, creature part and color it as white + add(t, os.date('%H:%M') .. ' ' .. split[1]..": ", "#FFFFFF", true) + add(messageT, split[1]..": ", "#FFFFFF", true) + + -- main part + if re ~= 0 then + for i=1,#re do + local data = re[i][2] -- each looted item + local formattedLoot = regexMatch(data, [[(^[^(]+)]])[1][1] + formattedLoot = formattedLoot:trim() + local amount = getFirstNumberInText(formattedLoot) -- amount found in data + local price = amount and getPrice(formattedLoot) * amount or getPrice(formattedLoot) -- if amount then multity price, else just take price + local color = getColor(price) -- generate hex string based off price + local messageColor = getColor(getPrice(formattedLoot)) + + combinedWorth = combinedWorth + price -- add all prices to calculate total worth + + add(t, data, color, i==#re) + add(messageT, data, color, i==#re) + + --drop tracker + for i, child in ipairs(dropTrackerWindow.contentsPanel:getChildren()) do + local childName = child.name + childName = childName and childName:getText() + + + if childName and formattedLoot:find(childName) then + trackedLoot[tostring(child.item:getItemId())] = trackedLoot[tostring(child.item:getItemId())] + (amount or 1) + child.drops:setText("Loot Drops: "..trackedLoot[tostring(child.item:getItemId())]) + + hightlightText(child.name,"#f0b400", 8) + modules.game_textmessage.messagesPanel.statusLabel:setVisible(true) + modules.game_textmessage.messagesPanel.statusLabel:setColoredText({ + "Valuable loot: ", "#f0b400", + childName.."", messageColor, + " dropped by "..name.."!", "#f0b400" + }) + schedule(3000, function() + modules.game_textmessage.messagesPanel.statusLabel:setVisible(false) + end) + end + end + end + end + + -- format total worth so it wont look obnoxious + if combinedWorth >= 1000000 then + div = combinedWorth/1000000 + formatted = math.floor(div) .. "." .. math.floor(div * 10) % 10 .. "kk" + elseif combinedWorth >= 1000 then + div = combinedWorth/1000 + formatted = math.floor(div) .. "." .. math.floor(div * 10) % 10 .. "k" + else + formatted = combinedWorth .. "gp" + end + + if modules.game_textmessage.messagesPanel.centerTextMessagePanel.highCenterLabel:getText() == text then + modules.game_textmessage.messagesPanel.centerTextMessagePanel.highCenterLabel:setColoredText(messageT) + schedule(math.max(#text * 50, 2000), function() + modules.game_textmessage.messagesPanel.centerTextMessagePanel.highCenterLabel:setVisible(false) + end) + end + + -- add total worth to string + add(t, " - (", "#FFFFFF", true) + add(t, formatted, getColor(combinedWorth), true) + add(t, ")", "#FFFFFF", true) + + -- get/create tab and write raw message + local tabName = "vBot Loot" + local tab = console.getTab(tabName) or console.addTab(tabName, true) + console.addText(text, console.SpeakTypesSettings, tabName, "") + + -- find last message in given tab and rewrite it with formatted string + local panel = console.consoleTabBar:getTabPanel(tab) + local consoleBuffer = panel:getChildById('consoleBuffer') + local message = consoleBuffer:getLastChild() + message:setColoredText(t) +end) + +local function niceFormat(v) + local div + local formatted + if v >= 10000000 then + div = v/10000000 + formatted = math.ceil(div) .. "M" + elseif v >= 1000000 then + div = v/1000000 + formatted = math.floor(div) .. "." .. math.floor(div * 10) % 10 .. "M" + elseif v >= 10000 then + div = v/1000 + formatted = math.floor(div) .. "k" + elseif v >= 1000 then + div = v/1000 + formatted = math.floor(div) .. "." .. math.floor(div * 10) % 10 .. "k" + else + formatted = v + end + return formatted +end + +resetAnalyzerSessionData = function() + launchTime = now + startExp = exp() + dmgTable = {} + healTable = {} + expTable = {} + totalDmg = 0 + totalHeal = 0 + dmgDistribution = {} + first = {l="-", r="0"} + second = {l="-", r="0"} + third = {l="-", r="0"} + fourth = {l="-", r="0"} + five = {l="-", r="0"} + lootedItems = {} + useData = {} + usedItems ={} + refreshLoot() + refreshWaste() + xpGraph:clear() + drawGraph(xpGraph, 0) + lootGraph:clear() + drawGraph(lootGraph, 0) + supplyGraph:clear() + drawGraph(supplyGraph, 0) + dmgGraph:clear() + drawGraph(dmgGraph, 0) + healGraph:clear() + drawGraph(healGraph, 0) + killList = {} + refreshKills() + HuntingSessionStart = os.date('%Y-%m-%d, %H:%M:%S') +end + +mainWindow.contentsPanel.ResetSession.onClick = function() + resetAnalyzerSessionData() +end + +mainWindow.contentsPanel.Settings.onClick = function() + settingsWindow:show() + settingsWindow:raise() + settingsWindow:focus() +end + + +-- extras window +settingsWindow.closeButton.onClick = function() + settingsWindow:hide() +end + +local function getFrame(v) + if v >= 1000000 then + return '/images/ui/rarity_gold' + elseif v >= 100000 then + return '/images/ui/rarity_purple' + elseif v >= 10000 then + return '/images/ui/rarity_blue' + elseif v >= 1000 then + return '/images/ui/rarity_green' + else + return '/images/ui/item' + end +end + +local function setFrames() + if not storage.analyzers.rarityFrames then return end + for _, container in pairs(getContainers()) do + local window = container.itemsPanel + for i, child in pairs(window:getChildren()) do + local id = child:getItemId() + + if id ~= 0 then -- there's item + local item = Item.create(id) + local name = item:getMarketData().name:lower() + + local price = getPrice(name) + -- set rarity frame + child:setImageSource(getFrame(price)) + else -- empty widget + -- revert any possible changes + child:setImageSource("/images/ui/item") + end + end + end +end +setFrames() + +onContainerOpen(function(container, previousContainer) + setFrames() +end) + +onAddItem(function(container, slot, item, oldItem) + setFrames() +end) + +onRemoveItem(function(container, slot, item) + setFrames() +end) + +onContainerUpdateItem(function(container, slot, item, oldItem) + setFrames() +end) + +function smallNumbers(n) + if n >= 10 ^ 6 then + return string.format("%.1fkk", n / 10 ^ 6) + elseif n >= 10 ^ 3 then + return string.format("%.1fk", n / 10 ^ 3) + else + return tostring(n) + end +end + +function refreshList() + local list = settingsWindow.CustomPrices + list:destroyChildren() + + for name, price in pairs(storage.analyzers.customPrices) do + local label = UI.createWidget("AnalyzerPriceLabel", list) + label.remove.onClick = function() + storage.analyzers.customPrices[name] = nil + label:destroy() + schedule(5, function() + setFrames() + end) + end + label:setText("["..name.."] = "..smallNumbers(price).." gp") + end +end +refreshList() + +settingsWindow.addItem.onClick = function() + local newPrices = storage.analyzers.customPrices + local id = settingsWindow.ID:getItemId() + local newPrice = tonumber(settingsWindow.NewPrice:getText()) + + if id < 100 then + return warn("No item added!") + end + + local name = Item.create(id):getMarketData().name + + if newPrices[name] then + return warn("Item already added! Remove it from the list to set a new price!") + end + + newPrices[name] = newPrice + settingsWindow.ID:setItemId(0) + settingsWindow.NewPrice:setText(0) + schedule(5, function() + setFrames() + end) + refreshList() +end + +settingsWindow.LootChannel:setOn(storage.analyzers.lootChannel) +settingsWindow.LootChannel.onClick = function(widget) + storage.analyzers.lootChannel = not storage.analyzers.lootChannel + widget:setOn(storage.analyzers.lootChannel) +end + +settingsWindow.RarityFrames:setOn(storage.analyzers.rarityFrames) +settingsWindow.RarityFrames.onClick = function(widget) + storage.analyzers.rarityFrames = not storage.analyzers.rarityFrames + widget:setOn(storage.analyzers.rarityFrames) + setFrames() +end + +local timeToLevel = function() + local t = 0 + if expPerHour(true) == 0 or expPerHour() == "-" then + return "-" + else + t = expLeft()/expPerHour(true) + return niceTimeFormat(math.ceil(t*60*60)) + end +end + +local sumT = function(t) + local s = 0 + for i,v in pairs(t) do + s = s + v.d + end + return s +end + +local valueInSeconds = function(t) + local d = 0 + local time = 0 + if #t > 0 then + for i, v in ipairs(t) do + if now - v.t <= 3000 then + if time == 0 then + time = v.t + end + d = d + v.d + else + table.remove(t, 1) + end + end + end + return math.ceil(d/((now-time)/1000)) +end + +local regex = "You lose ([0-9]*) hitpoints due to an attack by ([a-z]*) ([a-z A-z-]*)" +onTextMessage(function(mode, text) + local value = getFirstNumberInText(text) + if mode == 21 then -- damage dealt + totalDmg = totalDmg + value + table.insert(dmgTable, {d = value, t = now}) + if value > storage.bestHit then + storage.bestHit = value + end + end + if mode == 23 then -- healing + totalHeal = totalHeal + value + table.insert(healTable, {d = value, t = now}) + if value > storage.bestHeal then + storage.bestHeal = value + end + end + + -- damage distribution part + if text:find("You lose") then + local data = regexMatch(text, regex)[1] + if data then + local monster = data[4] + local val = data[2] + table.insert(dmgDistribution, {v=val,m=monster,t=now}) + end + end +end) + +function capitalFistLetter(str) + return (string.gsub(str, "^%l", string.upper)) +end + +-- tables maintance +macro(500, function() + local dmgFinal = {} + local labelTable = {} + local dmgSum = 0 + table.insert(expTable, exp()) + if #expTable > 15*60 then + for i,v in pairs(expTable) do + if i == 1 then + table.remove(expTable, i) + end + end + end + + for i,v in pairs(dmgDistribution) do + if now - v.t > 60*1000*10 then + table.remove(dmgDistribution, i) + else + dmgSum = dmgSum + v.v + if not dmgFinal[v.m] then + dmgFinal[v.m] = v.v + else + dmgFinal[v.m] = dmgFinal[v.m] + v.v + end + end + end + + first = dmgFinal[1] or {l="-", r="0"} + second = dmgFinal[2] or {l="-", r="0"} + third = dmgFinal[3] or {l="-", r="0"} + fourth = dmgFinal[4] or {l="-", r="0"} + five = dmgFinal[5] or {l="-", r="0"} + + for k,v in pairs(dmgFinal) do + table.insert(labelTable, {m=k, d=tonumber(v)}) + end + + table.sort(labelTable, function(a,b) return a.d > b.d end) + + for i,v in pairs(labelTable) do + local val = math.floor((v.d/dmgSum)*100) .. "%" + local words = string.split(v.m, " ") + local name = "" + for i, word in ipairs(words) do + name = name .. " " .. capitalFistLetter(word) + end + name = name:len() < 20 and name or name:sub(1,17).."..." + name = name:trim()..": " + if i == 1 then + first = {l=name, r=val} + elseif i == 2 then + second = {l=name, r=val} + elseif i == 3 then + third = {l=name, r=val} + elseif i == 4 then + fourth = {l=name, r=val} + elseif i == 5 then + five = {l=name, r=val} + else + break + end + end +end) + +function getPanelHeight(panel) + + local elements = panel.List:getChildCount() + if elements == 0 then + return 0 + else + local rows = math.ceil(elements/5) + local height = rows * 35 + return height + end +end + +function refreshLoot() + + lootItems:destroyChildren() + lootList:destroyChildren() + + for k,v in pairs(lootedItems) do + local label1 = UI.createWidget("AnalyzerLootItem", lootItems) + local price = v.count and getPrice(v.name) * v.count or getPrice(v.name) + + label1:setItemId(k) + label1:setItemCount(50) + label1:setShowCount(false) + label1.count:setText(niceFormat(v.count)) + label1.count:setColor(getColor(price)) + local tooltipName = v.count > 1 and v.name.."s" or v.name + label1:setTooltip(v.count .. "x " .. tooltipName .. " (Value: "..format_thousand(getPrice(v.name)).."gp, Sum: "..format_thousand(price).."gp)") + --hunting window loot list + local label2 = UI.createWidget("ListLabel", lootList) + label2:setText(v.count .. "x " .. v.name) + end + if lootItems:getChildCount() == 0 then + local label = UI.createWidget("ListLabel", lootList) + label:setText("None") + end +end +refreshLoot() + +function refreshKills() + killedList:destroyChildren() + local kills = 0 + for k,v in pairs(killList) do + kills = kills + 1 + local label = UI.createWidget("ListLabel", killedList) + label:setText(v .. "x " .. k) + end + + if kills == 0 then + local label = UI.createWidget("ListLabel", killedList) + label:setText("None") + end +end +refreshKills() + +function refreshWaste() + + supplyItems:destroyChildren() + for k,v in pairs(usedItems) do + local label1 = UI.createWidget("AnalyzerLootItem", supplyItems) + local price = v.count and getPrice(v.name) * v.count or getPrice(v.name) + + label1:setItemId(k) + label1:setItemCount(10023) + label1:setShowCount(false) + label1.count:setText(niceFormat(v.count)) + label1.count:setColor(getColor(price)) + local tooltipName = v.count > 1 and v.name.."s" or v.name + label1:setTooltip(v.count .. "x " .. tooltipName .. " (Value: "..format_thousand(getPrice(v.name)).."gp, Sum: "..format_thousand(price).."gp)") + end +end + +-- loot analyzer +-- adding +local containers = CaveBot.GetLootContainers() +local lastCap = freecap() +onAddItem(function(container, slot, item, oldItem) + if not table.find(containers, container:getContainerItem():getId()) then return end + if isInPz() then return end + if slot > 0 then return end + if freecap() >= lastCap then return end + local name = item:getId() + local tmpname = item:getId() == 3031 and "gold coin" or item:getId() == 3035 and "platinum coin" or item:getId() == 3043 and "crystal coin" or item:getMarketData().name + if not lootedItems[name] then + lootedItems[name] = { count = item:getCount(), name = tmpname } + else + lootedItems[name].count = lootedItems[name].count + item:getCount() + end + lastCap = freecap() + refreshLoot() + + -- drop tracker +end) + +onContainerUpdateItem(function(container, slot, item, oldItem) + if not table.find(containers, container:getContainerItem():getId()) then return end + if not oldItem then return end + if isInPz() then return end + if freecap() == lastCap then return end + + local tmpname = item:getId() == 3031 and "gold coin" or item:getId() == 3035 and "platinum coin" or item:getId() == 3043 and "crystal coin" or item:getMarketData().name + local amount = item:getCount() - oldItem:getCount() + if amount < 0 then + return + end + local name = item:getId() + if not lootedItems[name] then + lootedItems[name] = { count = amount, name = tmpname } + else + lootedItems[name].count = lootedItems[name].count + amount + end + lastCap = freecap() + refreshLoot() +end) + +-- ammo +local ammo = {16143, 763, 761, 7365, 3448, 762, 21470, 7364, 14251, 3447, 3449, 15793, 25757, 774, 35901, 6528, 7363, 3450, 16141, 25758, 14252, 3446, 16142, 35902} +onContainerUpdateItem(function(container, slot, item, oldItem) + local id = item:getId() + if not table.find(ammo, id) then return end + local newCount = item:getCount() + local oldCount = oldItem:getCount() + local name = item:getMarketData().name + + if oldCount - newCount == 1 then + if not usedItems[id] then + usedItems[id] = { count = 1, name = name} + else + usedItems[id].count = usedItems[id].count + 1 + end + refreshWaste() + end +end) + +-- waste +local regex3 = [[\d ([a-z A-Z]*)s...]] +onTextMessage(function(mode, text) + text = text:lower() + if not text:find("using one of") then return end + + local amount = getFirstNumberInText(text) + local re = regexMatch(text, regex3) + local name = re[1][2] + local id = WasteItems[name] + + if not useData[name] then + useData[name] = amount + else + if math.abs(useData[name]-amount) == 1 then + useData[name] = amount + if not usedItems[id] then + usedItems[id] = { count = 1, name = name} + else + usedItems[id].count = usedItems[id].count + 1 + end + else + useData[name] = amount + end + refreshWaste() + end +end) + +function hourVal(v) + v = v or 0 + return (v/uptime)*3600 +end + +function bottingStats() + lootWorth = 0 + wasteWorth = 0 + for k, v in pairs(lootedItems) do + if LootItems[v.name] then + lootWorth = lootWorth + (LootItems[v.name]*v.count) + end + end + for k, v in pairs(usedItems) do + if LootItems[v.name] then + wasteWorth = wasteWorth + (LootItems[v.name]*v.count) + end + end + balance = lootWorth - wasteWorth + + return lootWorth, wasteWorth, balance +end + +function bottingLabels(lootWorth, wasteWorth, balance) + balanceDesc = nil + hourDesc = nil + desc = nil + + if balance >= 1000000 or balance <= -1000000 then + desc = balance / 1000000 + balanceDesc = math.floor(desc) .. "." .. math.floor(desc * 10) % 10 .. "kk" + elseif balance >= 1000 or balance <= -1000 then + desc = balance / 1000 + balanceDesc = math.floor(desc) .. "." .. math.floor(desc * 10) % 10 .."k" + else + balanceDesc = balance .. "gp" + end + + hour = hourVal(balance) + if hour >= 1000000 or hour <= -1000000 then + desc = balance / 1000000 + hourDesc = math.floor(hourVal(desc)) .. "." .. math.floor(hourVal(desc) * 10) % 10 .. "kk/h" + elseif hour >= 1000 or hour <= -1000 then + desc = balance / 1000 + hourDesc = math.floor(hourVal(desc)) .. "." .. math.floor(hourVal(desc) * 10) % 10 .. "k/h" + else + hourDesc = math.floor(hourVal(balance)) .. "gp/h" + end + + return balanceDesc, hourDesc +end + +function reportStats() + local lootWorth, wasteWorth, balance = bottingStats() + local balanceDesc, hourDesc = bottingLabels(lootWorth, wasteWorth, balance) + + local a, b, c + + a = "Session Time: " .. sessionTime() .. ", Exp Gained: " .. format_thousand(expGained()) .. ", Exp/h: " .. expPerHour() + b = " | Balance: " .. balanceDesc .. " (" .. hourDesc .. ")" + c = a..b + + return c +end + +function damageHour() + if uptime < 5*60 then + return totalDmg + else + return hourVal(totalDmg) + end +end + +function healHour() + if uptime < 5*60 then + return totalHeal + else + return hourVal(totalHeal) + end +end + +function wasteHour() + local lootWorth, wasteWorth, balance = bottingStats() + if uptime < 5*60 then + return wasteWorth + else + return hourVal(wasteWorth) + end +end + + +function lootHour() + local lootWorth, wasteWorth, balance = bottingStats() + if uptime < 5*60 then + return lootWorth + else + return hourVal(lootWorth) + end +end + +function getHuntingData() + local lootWorth, wasteWorth, balance = bottingStats() + return totalDmg, totalHeal, lootWorth, wasteWorth, balance +end + +--bestdps/hps +local bestDPS = 0 +local bestHPS = 0 +--main loop +macro(500, function() + local lootWorth, wasteWorth, balance = bottingStats() + local balanceDesc, hourDesc = bottingLabels(lootWorth, wasteWorth, balance) + + -- hps and dps + local curHPS = valueInSeconds(healTable) + local curDPS = valueInSeconds(dmgTable) + + bestHPS = bestHPS > curHPS and bestHPS or curHPS + bestDPS = bestDPS > curDPS and bestDPS or curDPS + + --hunt window + sessionTimeLabel:setText(sessionTime()) + xpGainLabel:setText(format_thousand(expGained())) + xpHourLabel:setText(expPerHour()) + lootLabel:setText(format_thousand(lootWorth)) + suppliesLabel:setText(format_thousand(wasteWorth)) + balanceLabel:setColor(balance >= 0 and "#45ad25" or "#ff9854") + balanceLabel:setText(balanceDesc .. " (" .. hourDesc .. ")") + damageLabel:setText(format_thousand(totalDmg)) + damageHourLabel:setText(format_thousand(damageHour())) + healingLabel:setText(format_thousand(totalHeal)) + healingHourLabel:setText(format_thousand(healHour())) + + --loot window + lootInLootAnalyzerLabel:setText(format_thousand(lootWorth)) + lootHourInLootAnalyzerLabel:setText(format_thousand(lootHour())) + + + --supply window + suppliesInSuppliesAnalyzerLabel:setText(format_thousand(wasteWorth)) + suppliesHourInSuppliesAnalyzerLabel:setText(format_thousand(wasteHour())) + + --impact window + totalDamageLabel:setText(format_thousand(totalDmg)) + maxDpsLabel:setText(format_thousand(bestDPS)) + bestHitLabel:setText(storage.bestHit) + + top1.left:setText(first.l) + top1.right:setText(first.r) + top2.left:setText(second.l) + top2.right:setText(second.r) + top3.left:setText(third.l) + top3.right:setText(third.r) + top4.left:setText(fourth.l) + top4.right:setText(fourth.r) + top5.left:setText(five.l) + top5.right:setText(five.r) + + totalHealingLabel:setText(format_thousand(totalHeal)) + maxHpsLabel:setText(format_thousand(bestHPS)) + bestHealLabel:setText(storage.bestHeal) + + --xp window + xpGrainInXpLabel:setText(format_thousand(expGained())) + xpHourInXpLabel:setText(expPerHour()) + nextLevelLabel:setText(timeToLevel()) + progressBar:setPercent(modules.game_skills.skillsWindow.contentsPanel.level.percent:getPercent()) +end) + +--graphs, draw each minute +macro(60*1000, function() + + drawGraph(xpGraph, expPerHour(true) or 0) + drawGraph(lootGraph, lootHour() or 0) + drawGraph(supplyGraph, wasteHour() or 0) + drawGraph(dmgGraph, valueInSeconds(dmgTable) or 0) + drawGraph(healGraph, valueInSeconds(healTable) or 0) +end) + +--party hunt analyzer +macro(2000, function() + if not BotServer._websocket then return end + + -- send data + if storage.sendPartyAnalyzerData then + sendData() + end + + local totalWaste, totalLoot, totalBalance = getSumStats() + + partySessionTimeLabel:setText(sessionTime()) + partyLootLabel:setText(format_thousand(totalLoot)) + partySuppliesLabel:setText(format_thousand(totalWaste)) + partyBalanceLabel:setText(format_thousand(totalBalance)) + + if totalBalance < 0 then + partyBalanceLabel:setColor('#ff9854') + elseif totalBalance > 0 then + partyBalanceLabel:setColor('#45ad25') + else + partyBalanceLabel:setColor('white') + end +end) + +-- public functions +-- global namespace +Analyzer = {} + +Analyzer.getKillsAmount = function(name) + return killList[name] or 0 +end + +Analyzer.getLootedAmount = function(nameOrId) + if type(nameOrId) == "number" then + return lootedItems[nameOrId].count or 0 + else + local nameOrId = nameOrId:lower() + for k,v in pairs(lootedItems) do + if v.name == nameOrId then + return v.count + end + end + end + return 0 +end + +Analyzer.getTotalProfit = function() + local lootWorth, wasteWorth, balance = bottingStats() + + return lootWorth +end + +Analyzer.getTotalWaste = function() + local lootWorth, wasteWorth, balance = bottingStats() + + return wasteWorth +end + +Analyzer.getBalance = function() + local lootWorth, wasteWorth, balance = bottingStats() + + return balance +end + +Analyzer.getXpGained = function() + return expGained() +end + +Analyzer.getXpHour = function() + return expPerHour() +end + +Analyzer.getTimeToNextLevel = function() + return timeToLevel() +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/analyzer.otui b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/analyzer.otui new file mode 100644 index 0000000..fd12bd4 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/analyzer.otui @@ -0,0 +1,426 @@ +TrackerItem < Panel + height: 40 + + BotItem + id: item + anchors.top: parent.top + margin-top: 2 + anchors.left: parent.left + image-source: + + UIWidget + id: name + anchors.top: prev.top + margin-top: 1 + anchors.bottom: prev.verticalCenter + anchors.left: prev.right + anchors.right: parent.right + margin-left: 5 + text: Set Item to start track. + text-align:left + font: verdana-11px-rounded + color: #FFFFFF + + UIWidget + id: drops + anchors.top: prev.bottom + margin-top: 3 + anchors.bottom: Item.bottom + anchors.left: prev.left + anchors.right: parent.right + font: verdana-11px-rounded + text-align:left + text: Loot Drops: 0 + color: #CCCCCC + + +DualLabel < Label + height: 15 + text-offset: 4 0 + font: verdana-11px-rounded + text-align: left + width: 50 + + Label + id: value + anchors.right: parent.right + margin-right: 4 + anchors.verticalCenter: parent.verticalCenter + width: 200 + font: verdana-11px-rounded + text-align: right + text: 0 + +MemberWidget < Panel + height: 85 + margin-top: 3 + + UICreature + id: creature + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + size: 28 28 + + UIWidget + id: name + anchors.left: prev.right + margin-left: 5 + anchors.top: parent.top + height: 12 + anchors.right: parent.right + text: Player Name + font: verdana-11px-rounded + text-align: left + + ProgressBar + id: health + anchors.left: prev.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 2 + height: 7 + background-color: #00c000 + phantom: false + + ProgressBar + id: mana + anchors.left: prev.left + anchors.right: parent.right + anchors.top: prev.bottom + height: 7 + background-color: #0000FF + phantom: false + + DualLabel + id: balance + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 5 + text: Balance: + + DualLabel + id: damage + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 2 + text: Damage: + + DualLabel + id: healing + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 2 + text: Healing: + +AnalyzerPriceLabel < Label + background-color: alpha + text-offset: 2 0 + focusable: true + height: 16 + + $focus: + background-color: #00000055 + + Button + id: remove + !text: tr('x') + anchors.right: parent.right + margin-right: 15 + width: 15 + height: 15 + +AnalyzerListPanel < Panel + padding-left: 4 + padding-right: 4 + layout: + type: verticalBox + fit-children: true + + +ListLabel < Label + height: 15 + font: verdana-11px-rounded + text-offset: 15 0 + +AnalyzerItemsPanel < Panel + id: List + padding: 2 + layout: + type: grid + cell-size: 33 33 + cell-spacing: 1 + num-columns: 5 + fit-children: true + +AnalyzerLootItem < UIItem + opacity: 0.87 + height: 37 + margin-left: 1 + virtual: true + background-color: alpha + + Label + id: count + font: verdana-11px-rounded + color: white + opacity: 0.87 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + margin-right: 2 + text-align: right + text: 0 + +AnalyzerGraph < UIGraph + height: 140 + capacity: 400 + line-width: 1 + color: red + margin-top: 5 + margin-left: 5 + margin-right: 5 + background-color: #383636 + padding: 5 + font: verdana-11px-rounded + image-source: /images/ui/graph_background + +AnalyzerProgressBar < ProgressBar + background-color: green + height: 5 + margin-top: 3 + phantom: false + margin-left: 3 + margin-right: 3 + border: 1 black + +AnalyzerButton < Button + height: 22 + margin-bottom: 2 + font: verdana-11px-rounded + text-offset: 0 4 + +MainAnalyzerWindow < MiniWindow + id: MainAnalyzerWindow + text: Analytics Selector + height: 245 + icon: /images/topbuttons/analyzers + + MiniWindowContents + padding-left: 5 + padding-right: 5 + padding-top: 5 + layout: verticalBox + + AnalyzerButton + id: HuntingAnalyzer + text: Hunting Analyzer + + AnalyzerButton + id: LootAnalyzer + text: Loot Analyzer + + AnalyzerButton + id: SupplyAnalyzer + text: Supply Analyzer + + AnalyzerButton + id: ImpactAnalyzer + text: Impact Analyzer + + AnalyzerButton + id: XPAnalyzer + text: XP Analyzer + + AnalyzerButton + id: DropTracker + text: Drop Tracker + + AnalyzerButton + id: PartyHunt + text: Party Hunt + color: #3895D3 + + AnalyzerButton + id: Settings + text: Features & Settings + color: #FABD02 + + AnalyzerButton + id: ResetSession + text: Reset Session + color: #FF0000 + +HuntingAnalyzer < MiniWindow + id: HuntingAnalyzerWindow + text: Hunt Analyzer + icon: /images/topbuttons/analyzers + + MiniWindowContents + padding-top: 3 + layout: verticalBox + +LootAnalyzer < MiniWindow + id: LootAnalyzerWindow + text: Loot Analyzer + icon: /images/topbuttons/analyzers + + MiniWindowContents + padding-top: 3 + layout: verticalBox + +SupplyAnalyzer < MiniWindow + id: SupplyAnalyzerWindow + text: Supply Analyzer + icon: /images/topbuttons/analyzers + + MiniWindowContents + padding-top: 3 + layout: verticalBox + +ImpactAnalyzer < MiniWindow + id: ImpactAnalyzerWindow + text: Impact Analyzer + icon: /images/topbuttons/analyzers + + MiniWindowContents + padding-top: 3 + layout: verticalBox + +XPAnalyzer < MiniWindow + id: XPAnalyzerWindow + text: XP Analyzer + height: 150 + icon: /images/topbuttons/analyzers + + MiniWindowContents + padding-top: 3 + layout: verticalBox + +PartyAnalyzerWindow < MiniWindow + id: PartyAnalyzerWindow + text: Party Hunt + height: 200 + icon: /images/topbuttons/analyzers + + MiniWindowContents + padding-left: 3 + padding-right: 3 + padding-top: 1 + layout: verticalBox + +DropTracker < MiniWindow + id: DropTracker + text: Drop Tracker + height: 200 + icon: /images/topbuttons/analyzers + + MiniWindowContents + padding-left: 3 + padding-right: 3 + padding-top: 1 + layout: verticalBox + +FeaturesWindow < MainWindow + id: FeaturesWindow + size: 250 370 + padding: 15 + text: Analyzers Features + @onEscape: self:hide() + + TextList + id: CustomPrices + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + margin-top: 10 + padding: 1 + height: 220 + vertical-scrollbar: CustomPricesScrollBar + + VerticalScrollBar + id: CustomPricesScrollBar + anchors.top: CustomPrices.top + anchors.bottom: CustomPrices.bottom + anchors.right: CustomPrices.right + step: 14 + pixels-scroll: true + + BotItem + id: ID + anchors.left: CustomPrices.left + anchors.top: CustomPrices.bottom + margin-top: 5 + + SpinBox + id: NewPrice + anchors.left: prev.right + margin-left: 5 + anchors.verticalCenter: prev.verticalCenter + width: 100 + minimum: 0 + maximum: 1000000000 + step: 1 + text-align: center + focusable: true + + Button + id: addItem + anchors.left: prev.right + margin-left: 5 + anchors.verticalCenter: prev.verticalCenter + anchors.right: CustomPrices.right + text: Add + font: verdana-11px-rounded + + HorizontalSeparator + anchors.left: ID.right + margin-left: 5 + anchors.right: CustomPrices.right + anchors.verticalCenter: ID.top + + HorizontalSeparator + id: secondSeparator + anchors.left: ID.right + margin-left: 5 + anchors.right: CustomPrices.right + anchors.bottom: ID.bottom + + BotSwitch + id: LootChannel + anchors.left: CustomPrices.left + anchors.right: parent.horizontalCenter + margin-right: 2 + anchors.top: prev.top + margin-top: 20 + text: Loot Channel + font: verdana-11px-rounded + + BotSwitch + id: RarityFrames + anchors.left: parent.horizontalCenter + margin-left: 2 + anchors.right: CustomPrices.right + anchors.top: secondSeparator.top + margin-top: 20 + text: Rarity Frames + font: verdana-11px-rounded + + HorizontalSeparator + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: closeButton.top + margin-bottom: 8 + + Button + id: closeButton + !text: tr('Close') + font: cipsoftFont + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 45 21 + margin-top: 15 + margin-right: 5 \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/antiRs.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/antiRs.lua new file mode 100644 index 0000000..02160ac --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/antiRs.lua @@ -0,0 +1,33 @@ +setDefaultTab("Tools") +g_game.cancelAttackAndFollow() + +local frags = 0 +local unequip = false +local m = macro(50, "AntiRS & Msg", function() end) + +function safeExit() + CaveBot.setOff() + TargetBot.setOff() + g_game.cancelAttackAndFollow() + g_game.cancelAttackAndFollow() + g_game.cancelAttackAndFollow() + modules.game_interface.forceExit() +end + +onTextMessage(function(mode, text) + if not m.isOn() then return end + if not text:find("Warning! The murder of") then return end + frags = frags + 1 + if killsToRs() < 6 or frags > 1 then + EquipManager.setOff() + schedule(100, function() + local id = getLeft() and getLeft():getId() + + if id and not unequip then + unequip = true + g_game.equipItemId(id) + end + safeExit() + end) + end +end) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/cast_food.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/cast_food.lua new file mode 100644 index 0000000..187738d --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/cast_food.lua @@ -0,0 +1,22 @@ +setDefaultTab("HP") +if voc() ~= 1 and voc() ~= 11 then + if storage.foodItems then + local t = {} + for i, v in pairs(storage.foodItems) do + if not table.find(t, v.id) then + table.insert(t, v.id) + end + end + local foodItems = { 3607, 3585, 3592, 3600, 3601 } + for i, item in pairs(foodItems) do + if not table.find(t, item) then + table.insert(storage.foodItems, item) + end + end + end + macro(500, "Cast Food", function() + if player:getRegenerationTime() <= 400 then + cast("exevo pan", 5000) + end + end) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/cavebot.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/cavebot.lua new file mode 100644 index 0000000..ac9fd8f --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/cavebot.lua @@ -0,0 +1,52 @@ +-- Cavebot by otclient@otclient.ovh +-- visit http://bot.otclient.ovh/ + +local cavebotTab = "Cave" +local targetingTab = storage.extras.joinBot and "Cave" or "Target" + +setDefaultTab(cavebotTab) +CaveBot.Extensions = {} +importStyle("/cavebot/cavebot.otui") +importStyle("/cavebot/config.otui") +importStyle("/cavebot/editor.otui") +dofile("/cavebot/actions.lua") +dofile("/cavebot/config.lua") +dofile("/cavebot/editor.lua") +dofile("/cavebot/example_functions.lua") +dofile("/cavebot/recorder.lua") +dofile("/cavebot/walking.lua") +dofile("/cavebot/minimap.lua") +-- in this section you can add extensions, check extension_template.lua +--dofile("/cavebot/extension_template.lua") +dofile("/cavebot/sell_all.lua") +dofile("/cavebot/depositor.lua") +dofile("/cavebot/buy_supplies.lua") +dofile("/cavebot/d_withdraw.lua") +dofile("/cavebot/supply_check.lua") +dofile("/cavebot/travel.lua") +dofile("/cavebot/doors.lua") +dofile("/cavebot/pos_check.lua") +dofile("/cavebot/withdraw.lua") +dofile("/cavebot/inbox_withdraw.lua") +dofile("/cavebot/lure.lua") +dofile("/cavebot/bank.lua") +dofile("/cavebot/clear_tile.lua") +dofile("/cavebot/tasker.lua") +dofile("/cavebot/imbuing.lua") +-- main cavebot file, must be last +dofile("/cavebot/cavebot.lua") + +setDefaultTab(targetingTab) +if storage.extras.joinBot then UI.Label("-- [[ TargetBot ]] --") end +TargetBot = {} -- global namespace +importStyle("/targetbot/looting.otui") +importStyle("/targetbot/target.otui") +importStyle("/targetbot/creature_editor.otui") +dofile("/targetbot/creature.lua") +dofile("/targetbot/creature_attack.lua") +dofile("/targetbot/creature_editor.lua") +dofile("/targetbot/creature_priority.lua") +dofile("/targetbot/looting.lua") +dofile("/targetbot/walking.lua") +-- main targetbot file, must be last +dofile("/targetbot/target.lua") diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/cavebot_control_panel.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/cavebot_control_panel.lua new file mode 100644 index 0000000..765a640 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/cavebot_control_panel.lua @@ -0,0 +1,63 @@ +setDefaultTab("Cave") + +g_ui.loadUIFromString([[ +CaveBotControlPanel < Panel + margin-top: 5 + layout: + type: verticalBox + fit-children: true + + HorizontalSeparator + + Label + text-align: center + text: CaveBot Control Panel + font: verdana-11px-rounded + margin-top: 3 + + HorizontalSeparator + + Panel + id: buttons + margin-top: 2 + layout: + type: grid + cell-size: 86 20 + cell-spacing: 1 + flow: true + fit-children: true + + HorizontalSeparator + margin-top: 3 +]]) + +local panel = UI.createWidget("CaveBotControlPanel") + +storage.caveBot = { + forceRefill = false, + backStop = false, + backTrainers = false, + backOffline = false +} + +-- [[ B U T T O N S ]] -- + +local forceRefill = UI.Button("Force Refill", function(widget) + storage.caveBot.forceRefill = true + print("[CaveBot] Going back on refill on next supply check.") +end, panel.buttons) + +local backStop = UI.Button("Back & Stop", function(widget) + storage.caveBot.backStop = true + print("[CaveBot] Going back to city on next supply check and turning off CaveBot on depositer action.") +end, panel.buttons) + +local backTrainers = UI.Button("To Trainers", function(widget) + storage.caveBot.backTrainers = true + print("[CaveBot] Going back to city on next supply check and going to label 'toTrainers' on depositer action.") +end, panel.buttons) + +local backOffline = UI.Button("Offline", function(widget) + storage.caveBot.backOffline = true + print("[CaveBot] Going back to city on next supply check and going to label 'toOfflineTraining' on depositer action.") +end, panel.buttons) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/combo.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/combo.lua new file mode 100644 index 0000000..b97c118 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/combo.lua @@ -0,0 +1,443 @@ +setDefaultTab("Main") +local panelName = "combobot" +local ui = setupUI([[ +Panel + height: 19 + + BotSwitch + id: title + anchors.top: parent.top + anchors.left: parent.left + text-align: center + width: 130 + !text: tr('ComboBot') + + Button + id: combos + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 3 + height: 17 + text: Setup + +]]) +ui:setId(panelName) + +if not storage[panelName] then + storage[panelName] = { + enabled = false, + onSayEnabled = false, + onShootEnabled = false, + onCastEnabled = false, + followLeaderEnabled = false, + attackLeaderTargetEnabled = false, + attackSpellEnabled = false, + attackItemToggle = false, + sayLeader = "", + shootLeader = "", + castLeader = "", + sayPhrase = "", + spell = "", + serverLeader = "", + item = 3155, + attack = "", + follow = "", + commandsEnabled = true, + serverEnabled = false, + serverLeaderTarget = false, + serverTriggers = true + } +end + +local config = storage[panelName] + +ui.title:setOn(config.enabled) +ui.title.onClick = function(widget) +config.enabled = not config.enabled +widget:setOn(config.enabled) +end + +ui.combos.onClick = function(widget) + comboWindow:show() + comboWindow:raise() + comboWindow:focus() +end + +rootWidget = g_ui.getRootWidget() +if rootWidget then + comboWindow = UI.createWindow('ComboWindow', rootWidget) + comboWindow:hide() + + -- bot item + + comboWindow.actions.attackItem:setItemId(config.item) + comboWindow.actions.attackItem.onItemChange = function(widget) + config.item = widget:getItemId() + end + + -- switches + + comboWindow.actions.commandsToggle:setOn(config.commandsEnabled) + comboWindow.actions.commandsToggle.onClick = function(widget) + config.commandsEnabled = not config.commandsEnabled + widget:setOn(config.commandsEnabled) + end + + comboWindow.server.botServerToggle:setOn(config.serverEnabled) + comboWindow.server.botServerToggle.onClick = function(widget) + config.serverEnabled = not config.serverEnabled + widget:setOn(config.serverEnabled) + end + + comboWindow.server.Triggers:setOn(config.serverTriggers) + comboWindow.server.Triggers.onClick = function(widget) + config.serverTriggers = not config.serverTriggers + widget:setOn(config.serverTriggers) + end + + comboWindow.server.targetServerLeaderToggle:setOn(config.serverLeaderTarget) + comboWindow.server.targetServerLeaderToggle.onClick = function(widget) + config.serverLeaderTarget = not config.serverLeaderTarget + widget:setOn(config.serverLeaderTarget) + end + + -- buttons + comboWindow.closeButton.onClick = function(widget) + comboWindow:hide() + end + + -- combo boxes + + comboWindow.actions.followLeader:setOption(config.follow) + comboWindow.actions.followLeader.onOptionChange = function(widget) + config.follow = widget:getCurrentOption().text + end + + comboWindow.actions.attackLeaderTarget:setOption(config.attack) + comboWindow.actions.attackLeaderTarget.onOptionChange = function(widget) + config.attack = widget:getCurrentOption().text + end + + -- checkboxes + comboWindow.trigger.onSayToggle:setChecked(config.onSayEnabled) + comboWindow.trigger.onSayToggle.onClick = function(widget) + config.onSayEnabled = not config.onSayEnabled + widget:setChecked(config.onSayEnabled) + end + + comboWindow.trigger.onShootToggle:setChecked(config.onShootEnabled) + comboWindow.trigger.onShootToggle.onClick = function(widget) + config.onShootEnabled = not config.onShootEnabled + widget:setChecked(config.onShootEnabled) + end + + comboWindow.trigger.onCastToggle:setChecked(config.onCastEnabled) + comboWindow.trigger.onCastToggle.onClick = function(widget) + config.onCastEnabled = not config.onCastEnabled + widget:setChecked(config.onCastEnabled) + end + + comboWindow.actions.followLeaderToggle:setChecked(config.followLeaderEnabled) + comboWindow.actions.followLeaderToggle.onClick = function(widget) + config.followLeaderEnabled = not config.followLeaderEnabled + widget:setChecked(config.followLeaderEnabled) + end + + comboWindow.actions.attackLeaderTargetToggle:setChecked(config.attackLeaderTargetEnabled) + comboWindow.actions.attackLeaderTargetToggle.onClick = function(widget) + config.attackLeaderTargetEnabled = not config.attackLeaderTargetEnabled + widget:setChecked(config.attackLeaderTargetEnabled) + end + + comboWindow.actions.attackSpellToggle:setChecked(config.attackSpellEnabled) + comboWindow.actions.attackSpellToggle.onClick = function(widget) + config.attackSpellEnabled = not config.attackSpellEnabled + widget:setChecked(config.attackSpellEnabled) + end + + comboWindow.actions.attackItemToggle:setChecked(config.attackItemEnabled) + comboWindow.actions.attackItemToggle.onClick = function(widget) + config.attackItemEnabled = not config.attackItemEnabled + widget:setChecked(config.attackItemEnabled) + end + + -- text edits + comboWindow.trigger.onSayLeader:setText(config.sayLeader) + comboWindow.trigger.onSayLeader.onTextChange = function(widget, text) + config.sayLeader = text + end + + comboWindow.trigger.onShootLeader:setText(config.shootLeader) + comboWindow.trigger.onShootLeader.onTextChange = function(widget, text) + config.shootLeader = text + end + + comboWindow.trigger.onCastLeader:setText(config.castLeader) + comboWindow.trigger.onCastLeader.onTextChange = function(widget, text) + config.castLeader = text + end + + comboWindow.trigger.onSayPhrase:setText(config.sayPhrase) + comboWindow.trigger.onSayPhrase.onTextChange = function(widget, text) + config.sayPhrase = text + end + + comboWindow.actions.attackSpell:setText(config.spell) + comboWindow.actions.attackSpell.onTextChange = function(widget, text) + config.spell = text + end + + comboWindow.server.botServerLeader:setText(config.serverLeader) + comboWindow.server.botServerLeader.onTextChange = function(widget, text) + config.serverLeader = text + end +end + +-- bot server +-- [[ join party made by Frosty ]] -- + +local shouldCloseWindow = false +local firstInvitee = true +local isInComboTeam = false +macro(10, function() + if shouldCloseWindow and config.serverEnabled and config.enabled then + local channelsWindow = modules.game_console.channelsWindow + if channelsWindow then + local child = channelsWindow:getChildById("buttonCancel") + if child then + child:onClick() + shouldCloseWindow = false + isInComboTeam = true + end + end + end +end) + +comboWindow.server.partyButton.onClick = function(widget) + if config.serverEnabled and config.enabled then + if config.serverLeader:len() > 0 and storage.BotServerChannel:len() > 0 then + talkPrivate(config.serverLeader, "request invite " .. storage.BotServerChannel) + else + error("Request failed. Lack of data.") + end + end +end + +onTextMessage(function(mode, text) + if config.serverEnabled and config.enabled then + if mode == 20 then + if string.find(text, "invited you to") then + local regex = "[a-zA-Z]*" + local regexData = regexMatch(text, regex) + if regexData[1][1]:lower() == config.serverLeader:lower() then + local leader = getCreatureByName(regexData[1][1]) + if leader then + g_game.partyJoin(leader:getId()) + g_game.requestChannels() + g_game.joinChannel(1) + shouldCloseWindow = true + end + end + end + end + end +end) + +onTalk(function(name, level, mode, text, channelId, pos) + if config.serverEnabled and config.enabled then + if mode == 4 then + if string.find(text, "request invite") then + local access = string.match(text, "%d.*") + if access and access == storage.BotServerChannel then + local minion = getCreatureByName(name) + if minion then + g_game.partyInvite(minion:getId()) + if firstInvitee then + g_game.requestChannels() + g_game.joinChannel(1) + shouldCloseWindow = true + firstInvitee = false + end + end + else + talkPrivate(name, "Incorrect access key!") + end + end + end + end + -- [[ End of Frosty's Code ]] -- + if config.enabled and config.enabled then + if name:lower() == config.sayLeader:lower() and string.find(text, config.sayPhrase) and config.onSayEnabled then + startCombo = true + end + if (config.castLeader and name:lower() == config.castLeader:lower()) and isAttSpell(text) and config.onCastEnabled then + startCombo = true + end + end + if config.enabled and config.commandsEnabled and (config.shootLeader and name:lower() == config.shootLeader:lower()) or (config.sayLeader and name:lower() == config.sayLeader:lower()) or (config.castLeader and name:lower() == config.castLeader:lower()) then + if string.find(text, "ue") then + say(config.spell) + elseif string.find(text, "sd") then + local params = string.split(text, ",") + if #params == 2 then + local target = params[2]:trim() + if getCreatureByName(target) then + useWith(3155, getCreatureByName(target)) + end + end + elseif string.find(text, "att") then + local attParams = string.split(text, ",") + if #attParams == 2 then + local atTarget = attParams[2]:trim() + if getCreatureByName(atTarget) and config.attack == "COMMAND TARGET" then + g_game.attack(getCreatureByName(atTarget)) + end + end + end + end + if isAttSpell(text) and config.enabled and config.serverEnabled then + BotServer.send("trigger", "start") + end +end) + +onMissle(function(missle) + if config.enabled and config.onShootEnabled then + if not config.shootLeader or config.shootLeader:len() == 0 then + return + end + local src = missle:getSource() + if src.z ~= posz() then + return + end + local from = g_map.getTile(src) + local to = g_map.getTile(missle:getDestination()) + if not from or not to then + return + end + local fromCreatures = from:getCreatures() + local toCreatures = to:getCreatures() + if #fromCreatures ~= 1 or #toCreatures ~= 1 then + return + end + local c1 = fromCreatures[1] + local t1 = toCreatures[1] + leaderTarget = t1 + if c1:getName():lower() == config.shootLeader:lower() then + if config.attackItemEnabled and config.item and config.item > 100 and findItem(config.item) then + useWith(config.item, t1) + end + if config.attackSpellEnabled and config.spell:len() > 1 then + say(config.spell) + end + end + end +end) + +macro(10, function() + if not config.enabled or not config.attackLeaderTargetEnabled then return end + if leaderTarget and config.attack == "LEADER TARGET" then + if not getTarget() or (getTarget() and getTarget():getName() ~= leaderTarget:getName()) then + g_game.attack(leaderTarget) + end + end + if config.enabled and config.serverEnabled and config.attack == "SERVER LEADER TARGET" and serverTarget then + if serverTarget and not getTarget() or (getTarget() and getTarget():getname() ~= serverTarget) + then + g_game.attack(serverTarget) + end + end +end) + + +local toFollow +local toFollowPos = {} + +macro(100, function() + toFollow = nil + if not config.enabled or not config.followLeaderEnabled then return end + if leaderTarget and config.follow == "LEADER TARGET" and leaderTarget:isPlayer() then + toFollow = leaderTarget:getName() + elseif config.follow == "SERVER LEADER TARGET" and config.serverLeader:len() ~= 0 then + toFollow = serverTarget + elseif config.follow == "SERVER LEADER" and config.serverLeader:len() ~= 0 then + toFollow = config.serverLeader + elseif config.follow == "LEADER" then + if config.onSayEnabled and config.sayLeader:len() ~= 0 then + toFollow = config.sayLeader + elseif config.onCastEnabled and config.castLeader:len() ~= 0 then + toFollow = config.castLeader + elseif config.onShootEnabled and config.shootLeader:len() ~= 0 then + toFollow = config.shootLeader + end + end + if not toFollow then return end + local target = getCreatureByName(toFollow) + if target then + local tpos = target:getPosition() + toFollowPos[tpos.z] = tpos + end + if player:isWalking() then return end + local p = toFollowPos[posz()] + if not p then return end + if CaveBot.walkTo(p, 20, {ignoreNonPathable=true, precision=1, ignoreStairs=false}) then + delay(100) + end +end) + +onCreaturePositionChange(function(creature, oldPos, newPos) + if creature:getName() == toFollow and newPos then + toFollowPos[newPos.z] = newPos + end +end) + +local timeout = now +macro(10, function() + if config.enabled and startCombo then + if config.attackItemEnabled and config.item and config.item > 100 and findItem(config.item) then + useWith(config.item, getTarget()) + end + if config.attackSpellEnabled and config.spell:len() > 1 then + say(config.spell) + end + startCombo = false + end + -- attack part / server + if BotServer._websocket and config.enabled and config.serverEnabled then + if target() and now - timeout > 500 then + targetPos = target():getName() + BotServer.send("target", targetPos) + timeout = now + end + end +end) + +onUseWith(function(pos, itemId, target, subType) + if BotServer._websocket and itemId == 3155 then + BotServer.send("useWith", target:getPosition()) + end +end) + +if BotServer._websocket and config.enabled and config.serverEnabled then + BotServer.listen("trigger", function(name, message) + if message == "start" and name:lower() ~= player:getName():lower() and name:lower() == config.serverLeader:lower() and config.serverTriggers then + startCombo = true + end + end) + BotServer.listen("target", function(name, message) + if name:lower() ~= player:getName():lower() and name:lower() == config.serverLeader:lower() then + if not target() or target():getName() == getCreatureByName(message) then + if config.serverLeaderTarget then + serverTarget = getCreatureByName(message) + g_game.attack(getCreatureByName(message)) + end + end + end + end) + BotServer.listen("useWith", function(name, message) + local tile = g_map.getTile(message) + if config.serverTriggers and name:lower() ~= player:getName():lower() and name:lower() == config.serverLeader:lower() and config.attackItemEnabled and config.item and findItem(config.item) then + useWith(config.item, tile:getTopUseThing()) + end + end) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/combo.otui b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/combo.otui new file mode 100644 index 0000000..b89013a --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/combo.otui @@ -0,0 +1,391 @@ +AttackComboBoxPopupMenu < ComboBoxPopupMenu +AttackComboBoxPopupMenuButton < ComboBoxPopupMenuButton +AttackComboBox < ComboBox + @onSetup: | + self:addOption("LEADER TARGET") + self:addOption("COMMAND TARGET") + +FollowComboBoxPopupMenu < ComboBoxPopupMenu +FollowComboBoxPopupMenuButton < ComboBoxPopupMenuButton +FollowComboBox < ComboBox + @onSetup: | + self:addOption("LEADER TARGET") + self:addOption("SERVER LEADER TARGET") + self:addOption("LEADER") + self:addOption("SERVER LEADER") + +ComboTrigger < Panel + id: trigger + image-source: /images/ui/panel_flat + image-border: 6 + padding: 3 + size: 450 72 + + Label + id: triggerLabel1 + anchors.left: parent.left + anchors.top: parent.top + text: On Say + margin-top: 8 + margin-left: 5 + color: #ffaa00 + + Label + id: leaderLabel + anchors.left: triggerLabel1.right + anchors.top: triggerLabel1.top + text: Leader: + margin-left: 35 + + TextEdit + id: onSayLeader + anchors.left: leaderLabel.right + anchors.top: leaderLabel.top + anchors.bottom: leaderLabel.bottom + margin-left: 5 + width: 120 + font: cipsoftFont + + Label + id: phrase + anchors.left: onSayLeader.right + anchors.top: onSayLeader.top + text: Phrase: + margin-left: 5 + + TextEdit + id: onSayPhrase + anchors.left: phrase.right + anchors.top: leaderLabel.top + anchors.bottom: leaderLabel.bottom + margin-left: 5 + width: 120 + font: cipsoftFont + + CheckBox + id: onSayToggle + anchors.left: onSayPhrase.right + anchors.top: onSayPhrase.top + margin-top: 1 + margin-left: 5 + + Label + id: triggerLabel2 + anchors.left: triggerLabel1.left + anchors.top: triggerLabel1.bottom + text: On Shoot + margin-top: 5 + color: #ffaa00 + + Label + id: leaderLabel1 + anchors.left: triggerLabel2.right + anchors.top: triggerLabel2.top + text: Leader: + margin-left: 24 + + TextEdit + id: onShootLeader + anchors.left: leaderLabel1.right + anchors.top: leaderLabel1.top + anchors.bottom: leaderLabel1.bottom + anchors.right: onSayPhrase.right + margin-left: 5 + width: 120 + font: cipsoftFont + + CheckBox + id: onShootToggle + anchors.left: onShootLeader.right + anchors.top: onShootLeader.top + margin-top: 1 + margin-left: 5 + + Label + id: triggerLabel3 + anchors.left: triggerLabel2.left + anchors.top: triggerLabel2.bottom + text: On Cast + margin-top: 5 + color: #ffaa00 + + Label + id: leaderLabel2 + anchors.left: triggerLabel3.right + anchors.top: triggerLabel3.top + text: Leader: + margin-left: 32 + + TextEdit + id: onCastLeader + anchors.left: leaderLabel2.right + anchors.top: leaderLabel2.top + anchors.bottom: leaderLabel2.bottom + anchors.right: onSayPhrase.right + margin-left: 5 + width: 120 + font: cipsoftFont + + CheckBox + id: onCastToggle + anchors.left: onCastLeader.right + anchors.top: onCastLeader.top + margin-top: 1 + margin-left: 5 + +ComboActions < Panel + id: actions + image-source: /images/ui/panel_flat + image-border: 6 + padding: 3 + size: 220 100 + + Label + id: label1 + anchors.left: parent.left + anchors.top: parent.top + text: Follow: + margin-top: 5 + margin-left: 3 + height: 15 + color: #ffaa00 + + FollowComboBox + id: followLeader + anchors.left: prev.right + anchors.top: prev.top + margin-left: 7 + height: 15 + width: 145 + font: cipsoftFont + + CheckBox + id: followLeaderToggle + anchors.left: followLeader.right + anchors.top: followLeader.top + margin-top: 2 + margin-left: 5 + + Label + id: label2 + anchors.left: label1.left + anchors.top: label1.bottom + margin-top: 5 + text: Attack: + color: #ffaa00 + + AttackComboBox + id: attackLeaderTarget + anchors.left: prev.right + anchors.top: prev.top + margin-left: 5 + height: 15 + width: 145 + font: cipsoftFont + + CheckBox + id: attackLeaderTargetToggle + anchors.left: attackLeaderTarget.right + anchors.top: attackLeaderTarget.top + margin-top: 2 + margin-left: 5 + + Label + id: label3 + anchors.left: label2.left + anchors.top: label2.bottom + margin-top: 5 + text: Spell: + color: #ffaa00 + + TextEdit + id: attackSpell + anchors.left: prev.right + anchors.top: prev.top + anchors.right: attackLeaderTarget.right + margin-left: 17 + height: 15 + width: 145 + font: cipsoftFont + + CheckBox + id: attackSpellToggle + anchors.left: attackSpell.right + anchors.top: attackSpell.top + margin-top: 2 + margin-left: 5 + + Label + id: label4 + anchors.left: label3.left + anchors.top: label3.bottom + margin-top: 15 + text: Attack Item: + color: #ffaa00 + + BotItem + id: attackItem + anchors.left: prev.right + anchors.verticalCenter: prev.verticalCenter + margin-left: 10 + + CheckBox + id: attackItemToggle + anchors.left: prev.right + anchors.verticalCenter: prev.verticalCenter + margin-left: 5 + + BotSwitch + id: commandsToggle + anchors.left: prev.right + anchors.top: attackItem.top + anchors.right: attackSpellToggle.right + anchors.bottom: attackItem.bottom + margin-left: 5 + text: Leader Commands + text-wrap: true + multiline: true + +BotServer < Panel + id: server + image-source: /images/ui/panel_flat + image-border: 6 + padding: 3 + size: 220 100 + + Label + id: labelX + anchors.left: parent.left + anchors.top: parent.top + text: Leader: + height: 15 + color: #ffaa00 + margin-left: 3 + margin-top: 5 + + TextEdit + id: botServerLeader + anchors.left: prev.right + anchors.top: prev.top + anchors.right: parent.right + margin-right: 3 + margin-left: 9 + height: 15 + font: cipsoftFont + + Button + id: partyButton + anchors.left: labelX.left + anchors.top: botServerLeader.bottom + margin-top: 5 + height: 30 + text: Join Party + text-wrap: true + multiline: true + + BotSwitch + id: botServerToggle + anchors.left: prev.right + anchors.top: botServerLeader.bottom + anchors.right: parent.right + height: 30 + margin-left: 3 + margin-right: 3 + margin-top: 5 + text: Server Enabled + + BotSwitch + id: targetServerLeaderToggle + anchors.left: partyButton.left + anchors.top: partyButton.bottom + anchors.right: partyButton.right + margin-top: 3 + height: 30 + text: Leader Targets + + BotSwitch + id: Triggers + anchors.left: prev.right + anchors.top: partyButton.bottom + anchors.right: parent.right + margin-top: 3 + height: 30 + margin-left: 3 + margin-right: 3 + text: Triggers + +ComboWindow < MainWindow + !text: tr('Combo Options') + size: 500 280 + @onEscape: self:hide() + + ComboTrigger + id: trigger + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 7 + + Label + id: title + anchors.top: parent.top + anchors.left: parent.left + margin-left: 10 + text: Combo Trigger + color: #ff7700 + + ComboActions + id: actions + anchors.top: trigger.bottom + anchors.left: trigger.left + margin-top: 15 + + Label + id: title + anchors.top: parent.top + anchors.left: parent.left + margin-left: 10 + margin-top: 85 + text: Combo Actions + color: #ff7700 + + BotServer + id: server + anchors.top: actions.top + anchors.left: actions.right + margin-left: 10 + + Label + id: title + anchors.top: parent.top + anchors.left: server.left + margin-left: 3 + margin-top: 85 + text: BotServer + color: #ff7700 + + HorizontalSeparator + id: separator + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: closeButton.top + margin-bottom: 8 + + Button + id: closeButton + !text: tr('Close') + font: cipsoftFont + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 45 21 + margin-top: 15 + margin-right: 5 + + Button + id: toolsButton + !text: tr('Help') + font: cipsoftFont + anchors.right: closeButton.left + anchors.top: closeButton.top + margin-right: 10 + size: 45 21 + @onClick: g_platform.openUrl("http://bot.otclient.ovh/books/scripts/page/combobot") \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/configs.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/configs.lua new file mode 100644 index 0000000..45cd29a --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/configs.lua @@ -0,0 +1,97 @@ +--[[ + Configs for modules + Based on Kondrah storage method +--]] +local configName = modules.game_bot.contentsPanel.config:getCurrentOption().text + +-- make vBot config dir +if not g_resources.directoryExists("/bot/".. configName .."/vBot_configs/") then + g_resources.makeDir("/bot/".. configName .."/vBot_configs/") +end + +-- make profile dirs +for i=1,10 do + local path = "/bot/".. configName .."/vBot_configs/profile_"..i + if not g_resources.directoryExists(path) then + g_resources.makeDir(path) + end +end + +local profile = g_settings.getNumber('profile') + +HealBotConfig = {} +local healBotFile = "/bot/" .. configName .. "/vBot_configs/profile_".. profile .. "/HealBot.json" +AttackBotConfig = {} +local attackBotFile = "/bot/" .. configName .. "/vBot_configs/profile_".. profile .. "/AttackBot.json" +SuppliesConfig = {} +local suppliesFile = "/bot/" .. configName .. "/vBot_configs/profile_".. profile .. "/Supplies.json" + + +--healbot +if g_resources.fileExists(healBotFile) then + local status, result = pcall(function() + return json.decode(g_resources.readFileContents(healBotFile)) + end) + if not status then + return onError("Error while reading config file (" .. healBotFile .. "). To fix this problem you can delete HealBot.json. Details: " .. result) + end + HealBotConfig = result +end + +--attackbot +if g_resources.fileExists(attackBotFile) then + local status, result = pcall(function() + return json.decode(g_resources.readFileContents(attackBotFile)) + end) + if not status then + return onError("Error while reading config file (" .. attackBotFile .. "). To fix this problem you can delete HealBot.json. Details: " .. result) + end + AttackBotConfig = result +end + +--supplies +if g_resources.fileExists(suppliesFile) then + local status, result = pcall(function() + return json.decode(g_resources.readFileContents(suppliesFile)) + end) + if not status then + return onError("Error while reading config file (" .. suppliesFile .. "). To fix this problem you can delete HealBot.json. Details: " .. result) + end + SuppliesConfig = result +end + +function vBotConfigSave(file) + -- file can be either + --- heal + --- atk + --- supply + local configFile + local configTable + if not file then return end + file = file:lower() + if file == "heal" then + configFile = healBotFile + configTable = HealBotConfig + elseif file == "atk" then + configFile = attackBotFile + configTable = AttackBotConfig + elseif file == "supply" then + configFile = suppliesFile + configTable = SuppliesConfig + else + return + end + + local status, result = pcall(function() + return json.encode(configTable, 2) + end) + if not status then + return onError("Error while saving config. it won't be saved. Details: " .. result) + end + + if result:len() > 100 * 1024 * 1024 then + return onError("config file is too big, above 100MB, it won't be saved") + end + + g_resources.writeFileContents(configFile, result) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/depositer_config.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/depositer_config.lua new file mode 100644 index 0000000..568c825 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/depositer_config.lua @@ -0,0 +1,123 @@ +setDefaultTab("Cave") +local panelName = "specialDeposit" +local depositerPanel + +UI.Button("Stashing Settings", function() + depositerPanel:show() + depositerPanel:raise() + depositerPanel:focus() +end) + +if not storage[panelName] then + storage[panelName] = { + items = {}, + height = 380 + } +end + +local config = storage[panelName] + +depositerPanel = UI.createWindow('DepositerPanel', rootWidget) +depositerPanel:hide() +-- basic one +depositerPanel.CloseButton.onClick = function() + depositerPanel:hide() +end + +depositerPanel:setHeight(config.height or 380) +depositerPanel.onGeometryChange = function(widget, old, new) + if old.height == 0 then return end + config.height = new.height +end + +function arabicToRoman(n) + local t = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XI", "XII", "XIV", "XV", "XVI", "XVII"} + return t[n] +end + +local function refreshEntries() + depositerPanel.DepositerList:destroyChildren() + for _, entry in ipairs(config.items) do + local panel = g_ui.createWidget("StashItem", depositerPanel.DepositerList) + panel.name:setText(Item.create(entry.id):getMarketData().name) + for i, child in ipairs(panel:getChildren()) do + if child:getId() ~= "slot" then + child:setTooltip("Clear item or double click to remove entry.") + child.onDoubleClick = function(widget) + table.remove(config.items, table.find(entry)) + panel:destroy() + end + end + end + panel.item:setItemId(entry.id) + if entry.id > 0 then + panel.item:setImageSource('') + end + panel.item.onItemChange = function(widget) + local id = widget:getItemId() + if id < 100 then + table.remove(config.items, table.find(entry)) + panel:destroy() + else + for i, data in ipairs(config.items) do + if data.id == id then + warn("[Depositer Panel] Item already added!") + return + end + end + entry.id = id + panel.item:setImageSource('') + panel.name:setText(Item.create(entry.id):getMarketData().name) + if entry.index == 0 then + local window = modules.client_textedit.show(panel.slot, { + title = "Set depot for "..panel.name:getText(), + description = "Select depot to which item should be stashed, choose between 3 and 17", + validation = [[^([3-9]|1[0-7])$]] + }) + window.text:setText(entry.index) + schedule(50, function() + window:raise() + window:focus() + end) + end + end + end + if entry.id > 0 then + panel.slot:setText("Stash to depot: ".. entry.index) + end + panel.slot:setTooltip("Click to set stashing destination.") + panel.slot.onClick = function(widget) + local window = modules.client_textedit.show(widget, { + title = "Set depot for "..panel.name:getText(), + description = "Select depot to which item should be stashed, choose between 3 and 17", + validation = [[^([3-9]|1[0-7])$]] + }) + window.text:setText(entry.index) + schedule(50, function() + window:raise() + window:focus() + end) + end + panel.slot.onTextChange = function(widget, text) + local n = tonumber(text) + if n then + entry.index = n + widget:setText("Stash to depot: "..entry.index) + end + end + end +end +refreshEntries() + +depositerPanel.title.onDoubleClick = function(widget) + table.insert(config.items, {id=0, index=0}) + refreshEntries() +end + +function getStashingIndex(id) + for _, v in pairs(config.items) do + if v.id == id then + return v.index - 1 + end + end +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/depositer_config.otui b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/depositer_config.otui new file mode 100644 index 0000000..33dea64 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/depositer_config.otui @@ -0,0 +1,99 @@ +StashItem < Panel + height: 40 + + BotItem + id: item + anchors.top: parent.top + margin-top: 2 + anchors.left: parent.left + + UIWidget + id: name + anchors.top: prev.top + margin-top: 1 + anchors.bottom: prev.verticalCenter + anchors.left: prev.right + anchors.right: parent.right + margin-left: 5 + text-align:left + text: item name + font: verdana-11px-rounded + color: #FFFFFF + + UIWidget + id: slot + anchors.top: prev.bottom + margin-top: 3 + anchors.bottom: Item.bottom + anchors.left: prev.left + anchors.right: parent.right + font: verdana-11px-rounded + text-align:left + text: Add item to select locker. + color: #CCCCCC + +DepositerPanel < MainWindow + size: 230 380 + !text: tr('Depositer Panel') + @onEscape: self:hide() + + UIWidget + id: title + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text: Double click here to add item. + text-align: left + font: verdana-11px-rounded + color: #aeaeae + + ScrollablePanel + id: DepositerList + image-source: /images/ui/panel_flat + image-border: 1 + anchors.top: prev.bottom + margin-top: 5 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: sep.top + margin-bottom: 10 + padding: 2 + padding-left: 4 + vertical-scrollbar: DepositerScrollBar + layout: + type: verticalBox + + VerticalScrollBar + id: DepositerScrollBar + anchors.top: DepositerList.top + anchors.bottom: DepositerList.bottom + anchors.right: DepositerList.right + step: 14 + pixels-scroll: true + visible: false + + ResizeBorder + id: bottomResizeBorder + anchors.fill: next + height: 3 + minimum: 180 + maximum: 800 + margin-left: 3 + margin-right: 3 + background: #ffffff88 + + HorizontalSeparator + id: sep + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: CloseButton.top + margin-bottom: 8 + + Button + id: CloseButton + !text: tr('Close') + font: cipsoftFont + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 45 21 + margin-right: 5 \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/depot_withdraw.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/depot_withdraw.lua new file mode 100644 index 0000000..8323f71 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/depot_withdraw.lua @@ -0,0 +1,76 @@ +-- config +setDefaultTab("Tools") +local defaultBp = "shopping bag" +local id = 21411 + +-- script + +local playerContainer = nil +local depotContainer = nil +local mailContainer = nil + +function reopenLootContainer() + for _, container in pairs(getContainers()) do + if container:getName():lower() == defaultBp:lower() then + g_game.close(container) + end + end + + local lootItem = findItem(id) + if lootItem then + schedule(500, function() g_game.open(lootItem) end) + end + +end + +macro(50, "Depot Withdraw", function() + + -- set the containers + if not potionsContainer or not runesContainer or not ammoContainer then + for i, container in pairs(getContainers()) do + if container:getName() == defaultBp then + playerContainer = container + elseif string.find(container:getName(), "Depot") then + depotContainer = container + elseif string.find(container:getName(), "your inbox") then + mailContainer = container + end + end + end + + if playerContainer and #playerContainer:getItems() == 20 then + for j, item in pairs(playerContainer:getItems()) do + if item:getId() == id then + g_game.open(item, playerContainer) + return + end + end + end + + +if playerContainer and freecap() >= 200 then + local time = 500 + if depotContainer then + for i, container in pairs(getContainers()) do + if string.find(container:getName(), "Depot") then + for j, item in pairs(container:getItems()) do + g_game.move(item, playerContainer:getSlotPosition(playerContainer:getItemsCount()), item:getCount()) + return + end + end + end + end + + if mailContainer then + for i, container in pairs(getContainers()) do + if string.find(container:getName(), "your inbox") then + for j, item in pairs(container:getItems()) do + g_game.move(item, playerContainer:getSlotPosition(playerContainer:getItemsCount()), item:getCount()) + return + end + end + end + end +end + +end) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/eat_food.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/eat_food.lua new file mode 100644 index 0000000..27e9fea --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/eat_food.lua @@ -0,0 +1,28 @@ +setDefaultTab("HP") + +UI.Label("Eatable items:") +if type(storage.foodItems) ~= "table" then + storage.foodItems = {3582, 3577} +end + +local foodContainer = UI.Container(function(widget, items) + storage.foodItems = items +end, true) +foodContainer:setHeight(35) +foodContainer:setItems(storage.foodItems) + + +macro(500, "Eat Food", function() + if player:getRegenerationTime() > 400 or not storage.foodItems[1] then return end + -- search for food in containers + for _, container in pairs(g_game.getContainers()) do + for __, item in ipairs(container:getItems()) do + for i, foodItem in ipairs(storage.foodItems) do + if item:getId() == foodItem.id then + return g_game.use(item) + end + end + end + end +end) +UI.Separator() \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/equip.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/equip.lua new file mode 100644 index 0000000..b0c2f80 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/equip.lua @@ -0,0 +1,36 @@ +-- config +setDefaultTab("HP") +local scripts = 2 -- if you want more auto equip panels you can change 2 to higher value + +-- script by kondrah, don't edit below unless you know what you are doing +UI.Label("Auto equip") +if type(storage.autoEquip) ~= "table" then + storage.autoEquip = {} +end +for i=1,scripts do + if not storage.autoEquip[i] then + storage.autoEquip[i] = {on=false, title="Auto Equip", item1=i == 1 and 3052 or 0, item2=i == 1 and 3089 or 0, slot=i == 1 and 9 or 0} + end + UI.TwoItemsAndSlotPanel(storage.autoEquip[i], function(widget, newParams) + storage.autoEquip[i] = newParams + end) +end +macro(250, function() + local containers = g_game.getContainers() + for index, autoEquip in ipairs(storage.autoEquip) do + if autoEquip.on then + local slotItem = getSlot(autoEquip.slot) + if not slotItem or (slotItem:getId() ~= autoEquip.item1 and slotItem:getId() ~= autoEquip.item2) then + for _, container in pairs(containers) do + for __, item in ipairs(container:getItems()) do + if item:getId() == autoEquip.item1 or item:getId() == autoEquip.item2 then + g_game.move(item, {x=65535, y=autoEquip.slot, z=0}, item:getCount()) + delay(1000) -- don't call it too often + return + end + end + end + end + end + end +end) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/equipper.otui b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/equipper.otui new file mode 100644 index 0000000..d034146 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/equipper.otui @@ -0,0 +1,368 @@ +ConditionBoxPopupMenu < ComboBoxPopupMenu +ConditionBoxPopupMenuButton < ComboBoxPopupMenuButton +ConditionBox < ComboBox + @onSetup: | + self:addOption("-") + self:addOption("and") + self:addOption("or") + +PreButton < PreviousButton + background: #363636 + height: 15 + +NexButton < NextButton + background: #363636 + height: 15 + +CondidionLabel < FlatPanel + padding: 1 + height: 15 + + Label + id: text + anchors.fill: parent + text-align: center + font: verdana-11px-rounded + background: #363636 + +Rule < UIWidget + background-color: alpha + text-offset: 18 2 + focusable: true + height: 16 + text-align: left + font: verdana-11px-rounded + + CheckBox + id: enabled + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + width: 15 + height: 15 + margin-top: 2 + margin-left: 3 + tooltip: Entry enabled/disabled + + $focus: + background-color: #00000055 + + Button + id: remove + text: X + anchors.right: parent.right + margin-right: 15 + width: 14 + height: 14 + text-align: center + tooltip: Remove entry + anchors.verticalCenter: parent.verticalCenter + + Button + id: visible + text: V + anchors.right: prev.left + margin-right: 3 + width: 14 + height: 14 + text-align: center + tooltip: Items must be visible + anchors.verticalCenter: parent.verticalCenter + + +ConditionPanel < Panel + height: 53 + + NexButton + id: nex + anchors.top: parent.top + margin-top: 5 + anchors.right: parent.right + + PreButton + id: pre + anchors.top: parent.top + margin-top: 5 + anchors.left: parent.left + + CondidionLabel + id: description + anchors.top: parent.top + margin-top: 5 + anchors.left: prev.right + anchors.right: nex.left + margin-left: 3 + margin-right: 3 + + SpinBox + id: spinbox + anchors.top: description.bottom + margin-top: 5 + anchors.horizontalCenter: parent.horizontalCenter + width: 100 + text-align: center + minimum: 0 + maximum: 100 + step: 1 + focusable: true + + BotTextEdit + id: text + anchors.top: description.bottom + margin-top: 5 + anchors.horizontalCenter: parent.horizontalCenter + width: 200 + text-align: center + + + +ListPanel < FlatPanel + size: 270 300 + padding-left: 10 + padding-right: 10 + padding-bottom: 10 + + Label + id: title + anchors.verticalCenter: parent.top + anchors.left: parent.left + text: Rules List + font: verdana-11px-rounded + color: #FABD02 + + Label + id: mainLabel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + margin-top: 10 + margin-left: 2 + !text: tr('More important methods come first.') + text-align: left + font: verdana-11px-rounded + color: #aeaeae + + TextList + id: list + anchors.fill: parent + margin-top: 25 + margin-bottom: 18 + vertical-scrollbar: listScrollBar + padding: 2 + + VerticalScrollBar + id: listScrollBar + anchors.top: list.top + anchors.bottom: list.bottom + anchors.right: list.right + step: 14 + pixels-scroll: true + + Button + id: up + anchors.right: parent.right + anchors.top: list.bottom + size: 60 17 + text: Move Up + text-align: center + font: cipsoftFont + margin-top: 5 + + Button + id: down + anchors.right: prev.left + anchors.verticalCenter: prev.verticalCenter + size: 60 17 + margin-right: 5 + text: Move Down + text-align: center + font: cipsoftFont + + + +Unequip < Panel + height: 0 + padding: 5 + layout: + type: verticalBox + + CheckBox + text: Head Slot + + CheckBox + text: Neck Slot + + CheckBox + text: Torso Slot + + CheckBox + text: Left Hand Slot + + CheckBox + text: Right Hand Slot + + CheckBox + text: Legs Slot + + CheckBox + text: Finger Slot + + CheckBox + text: Ammo Slot + + CheckBox + text: Feet Slot + + + +InputPanel < FlatPanel + size: 270 300 + padding-left: 10 + padding-right: 10 + padding-bottom: 10 + + Label + id: title + anchors.verticalCenter: parent.top + anchors.left: parent.left + text: Input Panel + font: verdana-11px-rounded + color: #FF0000 + + Panel + id: itemBox + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + margin-top: 10 + height: 80 + layout: + type: grid + cell-size: 34 34 + cell-spacing: 2 + num-columns: 7 + + Button + id: unequip + anchors.top: prev.bottom + anchors.left: parent.left + text: Unequip + height: 16 + width: 70 + + Label + id: mainLabel + anchors.left: prev.right + anchors.right: parent.right + margin-top: 2 + anchors.verticalCenter: prev.verticalCenter + margin-left: 2 + !text: tr('& Equip above item(s) when:') + text-align: center + font: verdana-11px-rounded + color: #aeaeae + + Unequip + id: unequipPanel + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 4 + + ConditionPanel + id: condition + anchors.left: parent.left + anchors.right: parent.right + anchors.top: unequipPanel.bottom + margin-top: 8 + + HorizontalSeparator + anchors.verticalCenter: next.verticalCenter + anchors.left: parent.left + anchors.right: parent.right + + ConditionBox + id: useSecondCondition + anchors.top: condition.bottom + margin-top: 3 + anchors.horizontalCenter: parent.horizontalCenter + width: 50 + + ConditionPanel + id: optionalCondition + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 3 + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + + BotTextEdit + id: name + anchors.left: parent.left + anchors.bottom: parent.bottom + width: 175 + + Label + anchors.horizontalCenter: prev.horizontalCenter + anchors.bottom: prev.top + margin-bottom: 2 + text-align: center + text: Profile Name + font: verdana-11px-rounded + color: #aeaeae + + Button + id: addButton + anchors.top: name.top + anchors.bottom: name.bottom + anchors.left: name.right + margin-left: 3 + anchors.right: parent.right + text: Add + tooltip: On add above rule will be listed as Profile name - use friendly one! + +EquipWindow < MainWindow + size: 600 370 + text: Equipment Manager + @onEscape: self:hide() + + ListPanel + id: listPanel + anchors.left: parent.left + anchors.top: parent.top + + VerticalSeparator + anchors.top: parent.top + anchors.bottom: bottomSep.top + margin-bottom: 5 + anchors.horizontalCenter: parent.horizontalCenter + + InputPanel + id: inputPanel + anchors.right: parent.right + anchors.top: parent.top + + HorizontalSeparator + id: bottomSep + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: closeButton.top + margin-bottom: 8 + + Button + id: closeButton + !text: tr('Close') + font: cipsoftFont + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 45 21 + margin-top: 15 + margin-right: 5 \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/exeta.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/exeta.lua new file mode 100644 index 0000000..324bad9 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/exeta.lua @@ -0,0 +1,27 @@ +local voc = player:getVocation() +if voc == 1 or voc == 11 then + setDefaultTab("Cave") + UI.Separator() + local m = macro(100000, "Exeta when low hp", function() end) + local lastCast = now + onCreatureHealthPercentChange(function(creature, healthPercent) + if m.isOff() then return end + if healthPercent > 15 then return end + if CaveBot.isOff() or TargetBot.isOff() then return end + if modules.game_cooldown.isGroupCooldownIconActive(3) then return end + if creature:getPosition() and getDistanceBetween(pos(),creature:getPosition()) > 1 then return end + if canCast("exeta res") and now - lastCast > 6000 then + say("exeta res") + lastCast = now + end + end) + + macro(500, "ExetaIfPlayer", function() + if CaveBot.isOff() then return end + if getMonsters(1) >= 1 and getPlayers(6) > 0 then + say("exeta res") + delay(6000) + end + end) + UI.Separator() +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/extras.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/extras.lua new file mode 100644 index 0000000..e734948 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/extras.lua @@ -0,0 +1,642 @@ +setDefaultTab("Main") + +-- securing storage namespace +local panelName = "extras" +if not storage[panelName] then + storage[panelName] = {} +end +local settings = storage[panelName] + +-- basic elements +extrasWindow = UI.createWindow('ExtrasWindow', rootWidget) +extrasWindow:hide() +extrasWindow.closeButton.onClick = function(widget) + extrasWindow:hide() +end + +extrasWindow.onGeometryChange = function(widget, old, new) + if old.height == 0 then return end + + settings.height = new.height +end + +extrasWindow:setHeight(settings.height or 360) + +-- available options for dest param +local rightPanel = extrasWindow.content.right +local leftPanel = extrasWindow.content.left + +-- objects made by Kondrah - taken from creature editor, minor changes to adapt +local addCheckBox = function(id, title, defaultValue, dest, tooltip) + local widget = UI.createWidget('ExtrasCheckBox', dest) + widget.onClick = function() + widget:setOn(not widget:isOn()) + settings[id] = widget:isOn() + if id == "checkPlayer" then + local label = rootWidget.newHealer.targetSettings.vocations.title + if not widget:isOn() then + label:setColor("#d9321f") + label:setTooltip("! WARNING ! \nTurn on check players in extras to use this feature!") + else + label:setColor("#dfdfdf") + label:setTooltip("") + end + end + end + widget:setText(title) + widget:setTooltip(tooltip) + if settings[id] == nil then + widget:setOn(defaultValue) + else + widget:setOn(settings[id]) + end + settings[id] = widget:isOn() +end + +local addItem = function(id, title, defaultItem, dest, tooltip) + local widget = UI.createWidget('ExtrasItem', dest) + widget.text:setText(title) + widget.text:setTooltip(tooltip) + widget.item:setTooltip(tooltip) + widget.item:setItemId(settings[id] or defaultItem) + widget.item.onItemChange = function(widget) + settings[id] = widget:getItemId() + end + settings[id] = settings[id] or defaultItem +end + +local addTextEdit = function(id, title, defaultValue, dest, tooltip) + local widget = UI.createWidget('ExtrasTextEdit', dest) + widget.text:setText(title) + widget.textEdit:setText(settings[id] or defaultValue or "") + widget.text:setTooltip(tooltip) + widget.textEdit.onTextChange = function(widget,text) + settings[id] = text + end + settings[id] = settings[id] or defaultValue or "" +end + +local addScrollBar = function(id, title, min, max, defaultValue, dest, tooltip) + local widget = UI.createWidget('ExtrasScrollBar', dest) + widget.text:setTooltip(tooltip) + widget.scroll.onValueChange = function(scroll, value) + widget.text:setText(title .. ": " .. value) + if value == 0 then + value = 1 + end + settings[id] = value + end + widget.scroll:setRange(min, max) + widget.scroll:setTooltip(tooltip) + if max-min > 1000 then + widget.scroll:setStep(100) + elseif max-min > 100 then + widget.scroll:setStep(10) + end + widget.scroll:setValue(settings[id] or defaultValue) + widget.scroll.onValueChange(widget.scroll, widget.scroll:getValue()) +end + +UI.Button("vBot Settings and Scripts", function() + extrasWindow:show() + extrasWindow:raise() + extrasWindow:focus() +end) +UI.Separator() + +---- to maintain order, add options right after another: +--- add object +--- add variables for function (optional) +--- add callback (optional) +--- optionals should be addionaly sandboxed (if true then end) + +addItem("rope", "Rope Item", 9596, leftPanel, "This item will be used in various bot related scripts as default rope item.") +addItem("shovel", "Shovel Item", 9596, leftPanel, "This item will be used in various bot related scripts as default shovel item.") +addItem("machete", "Machete Item", 9596, leftPanel, "This item will be used in various bot related scripts as default machete item.") +addItem("scythe", "Scythe Item", 9596, leftPanel, "This item will be used in various bot related scripts as default scythe item.") +addScrollBar("talkDelay", "Global NPC Talk Delay", 0, 2000, 1000, leftPanel, "Breaks between each talk action in cavebot (time in miliseconds).") +addScrollBar("looting", "Max Loot Distance", 0, 50, 40, leftPanel, "Every loot corpse futher than set distance (in sqm) will be ignored and forgotten.") +addScrollBar("huntRoutes", "Hunting Rounds Limit", 0, 300, 50, leftPanel, "Round limit for supply check, if character already made more rounds than set, on next supply check will return to city.") +addScrollBar("killUnder", "Kill monsters below", 0, 100, 1, leftPanel, "Force TargetBot to kill added creatures when they are below set percentage of health - will ignore all other TargetBot settings.") +addScrollBar("gotoMaxDistance", "Max GoTo Distance", 0, 127, 30, leftPanel, "Maximum distance to next goto waypoint for the bot to try to reach.") +addCheckBox("lootLast", "Start loot from last corpse", true, leftPanel, "Looting sequence will be reverted and bot will start looting newest bodies.") +addCheckBox("joinBot", "Join TargetBot and CaveBot", false, leftPanel, "Cave and Target tabs will be joined into one.") +addCheckBox("reachable", "Target only pathable mobs", false, leftPanel, "Ignore monsters that can't be reached.") + +addCheckBox("title", "Custom Window Title", true, rightPanel, "Personalize OTCv8 window name according to character specific.") +if true then + local vocText = "" + + if voc() == 1 or voc() == 11 then + vocText = "- EK" + elseif voc() == 2 or voc() == 12 then + vocText = "- RP" + elseif voc() == 3 or voc() == 13 then + vocText = "- MS" + elseif voc() == 4 or voc() == 14 then + vocText = "- ED" + end + + macro(5000, function() + if settings.title then + if hppercent() > 0 then + g_window.setTitle("Tibia - " .. name() .. " - " .. lvl() .. "lvl " .. vocText) + else + g_window.setTitle("Tibia - " .. name() .. " - DEAD") + end + else + g_window.setTitle("Tibia - " .. name()) + end + end) +end + +addCheckBox("separatePm", "Open PM's in new Window", false, rightPanel, "PM's will be automatically opened in new tab after receiving one.") +if true then + onTalk(function(name, level, mode, text, channelId, pos) + if mode == 4 and settings.separatePm then + local g_console = modules.game_console + local privateTab = g_console.getTab(name) + if privateTab == nil then + privateTab = g_console.addTab(name, true) + g_console.addPrivateText(g_console.applyMessagePrefixies(name, level, text), g_console.SpeakTypesSettings['private'], name, false, name) + end + return + end + end) +end + +addTextEdit("useAll", "Use All Hotkey", "space", rightPanel, "Set hotkey for universal actions - rope, shovel, scythe, use, open doors") +if true then + local useId = { 34847, 1764, 21051, 30823, 6264, 5282, 20453, 20454, 20474, 11708, 11705, + 6257, 6256, 2772, 27260, 2773, 1632, 1633, 1948, 435, 6252, 6253, 5007, 4911, + 1629, 1630, 5108, 5107, 5281, 1968, 435, 1948, 5542, 31116, 31120, 30742, 31115, + 31118, 20474, 5737, 5736, 5734, 5733, 31202, 31228, 31199, 31200, 33262, 30824, + 5125, 5126, 5116, 5117, 8257, 8258, 8255, 8256, 5120, 30777, 30776, 23873, 23877, + 5736, 6264, 31262, 31130, 31129, 6250, 6249, 5122, 30049, 7131, 7132, 7727 } + local shovelId = { 606, 593, 867, 608 } + local ropeId = { 17238, 12202, 12935, 386, 421, 21966, 14238 } + local macheteId = { 2130, 3696 } + local scytheId = { 3653 } + + setDefaultTab("Tools") + -- script + if settings.useAll and settings.useAll:len() > 0 then + hotkey(settings.useAll, function() + if not modules.game_walking.wsadWalking then return end + for _, tile in pairs(g_map.getTiles(posz())) do + if distanceFromPlayer(tile:getPosition()) < 2 then + for _, item in pairs(tile:getItems()) do + -- use + if table.find(useId, item:getId()) then + use(item) + return + elseif table.find(shovelId, item:getId()) then + useWith(settings.shovel, item) + return + elseif table.find(ropeId, item:getId()) then + useWith(settings.rope, item) + return + elseif table.find(macheteId, item:getId()) then + useWith(settings.machete, item) + return + elseif table.find(scytheId, item:getId()) then + useWith(settings.scythe, item) + return + end + end + end + end + end) + end +end + + +addCheckBox("timers", "MW & WG Timers", true, rightPanel, "Show times for Magic Walls and Wild Growths.") +if true then + local activeTimers = {} + + onAddThing(function(tile, thing) + if not settings.timers then return end + if not thing:isItem() then + return + end + local timer = 0 + if thing:getId() == 2129 then -- mwall id + timer = 20000 -- mwall time + elseif thing:getId() == 2130 then -- wg id + timer = 45000 -- wg time + else + return + end + + local pos = tile:getPosition().x .. "," .. tile:getPosition().y .. "," .. tile:getPosition().z + if not activeTimers[pos] or activeTimers[pos] < now then + activeTimers[pos] = now + timer + end + tile:setTimer(activeTimers[pos] - now) + end) + + onRemoveThing(function(tile, thing) + if not settings.timers then return end + if not thing:isItem() then + return + end + if (thing:getId() == 2129 or thing:getId() == 2130) and tile:getGround() then + local pos = tile:getPosition().x .. "," .. tile:getPosition().y .. "," .. tile:getPosition().z + activeTimers[pos] = nil + tile:setTimer(0) + end + end) +end + + +addCheckBox("antiKick", "Anti - Kick", true, rightPanel, "Turn every 10 minutes to prevent kick.") +if true then + macro(600*1000, function() + if not settings.antiKick then return end + local dir = player:getDirection() + turn((dir + 1) % 4) + schedule(50, function() turn(dir) end) + end) +end + + +addCheckBox("stake", "Skin Monsters", false, leftPanel, "Automatically skin & stake corpses when cavebot is enabled") +if true then + local knifeBodies = {4286, 4272, 4173, 4011, 4025, 4047, 4052, 4057, 4062, 4112, 4212, 4321, 4324, 4327, 10352, 10356, 10360, 10364} + local stakeBodies = {4097, 4137, 8738, 18958} + local fishingBodies = {9582} + macro(500, function() + if not CaveBot.isOn() or not settings.stake then return end + for i, tile in ipairs(g_map.getTiles(posz())) do + local item = tile:getTopThing() + if item and item:isContainer() then + if table.find(knifeBodies, item:getId()) and findItem(5908) then + CaveBot.delay(550) + useWith(5908, item) + return + end + if table.find(stakeBodies, item:getId()) and findItem(5942) then + CaveBot.delay(550) + useWith(5942, item) + return + end + if table.find(fishingBodies, item:getId()) and findItem(3483) then + CaveBot.delay(550) + useWith(3483, item) + return + end + end + end + end) +end + + +addCheckBox("oberon", "Auto Reply Oberon", true, rightPanel, "Auto reply to Grand Master Oberon talk minigame.") +if true then + onTalk(function(name, level, mode, text, channelId, pos) + if not settings.oberon then return end + if mode == 34 then + if string.find(text, "world will suffer for") then + say("Are you ever going to fight or do you prefer talking?") + elseif string.find(text, "feet when they see me") then + say("Even before they smell your breath?") + elseif string.find(text, "from this plane") then + say("Too bad you barely exist at all!") + elseif string.find(text, "ESDO LO") then + say("SEHWO ASIMO, TOLIDO ESD") + elseif string.find(text, "will soon rule this world") then + say("Excuse me but I still do not get the message!") + elseif string.find(text, "honourable and formidable") then + say("Then why are we fighting alone right now?") + elseif string.find(text, "appear like a worm") then + say("How appropriate, you look like something worms already got the better of!") + elseif string.find(text, "will be the end of mortal") then + say("Then let me show you the concept of mortality before it!") + elseif string.find(text, "virtues of chivalry") then + say("Dare strike up a Minnesang and you will receive your last accolade!") + end + end + end) +end + + +addCheckBox("autoOpenDoors", "Auto Open Doors", true, rightPanel, "Open doors when trying to step on them.") +if true then + local doorsIds = { 5007, 8265, 1629, 1632, 5129, 6252, 6249, 7715, 7712, 7714, + 7719, 6256, 1669, 1672, 5125, 5115, 5124, 17701, 17710, 1642, + 6260, 5107, 4912, 6251, 5291, 1683, 1696, 1692, 5006, 2179, 5116, + 1632, 11705, 30772, 30774, 6248, 5735, 5732, 5120, 23873, 5736, + 6264, 5122, 30049, 30042, 7727 } + + function checkForDoors(pos) + local tile = g_map.getTile(pos) + if tile then + local useThing = tile:getTopUseThing() + if useThing and table.find(doorsIds, useThing:getId()) then + g_game.use(useThing) + end + end + end + + onKeyPress(function(keys) + local wsadWalking = modules.game_walking.wsadWalking + if not settings.autoOpenDoors then return end + local pos = player:getPosition() + if keys == 'Up' or (wsadWalking and keys == 'W') then + pos.y = pos.y - 1 + elseif keys == 'Down' or (wsadWalking and keys == 'S') then + pos.y = pos.y + 1 + elseif keys == 'Left' or (wsadWalking and keys == 'A') then + pos.x = pos.x - 1 + elseif keys == 'Right' or (wsadWalking and keys == 'D') then + pos.x = pos.x + 1 + elseif wsadWalking and keys == "Q" then + pos.y = pos.y - 1 + pos.x = pos.x - 1 + elseif wsadWalking and keys == "E" then + pos.y = pos.y - 1 + pos.x = pos.x + 1 + elseif wsadWalking and keys == "Z" then + pos.y = pos.y + 1 + pos.x = pos.x - 1 + elseif wsadWalking and keys == "C" then + pos.y = pos.y + 1 + pos.x = pos.x + 1 + end + checkForDoors(pos) + end) +end + + +addCheckBox("bless", "Buy bless at login", true, rightPanel, "Say !bless at login.") +if true then + local blessed = false + onTextMessage(function(mode,text) + if not settings.bless then return end + + text = text:lower() + + if text == "you already have all blessings." then + blessed = true + end + end) + if settings.bless then + if player:getBlessings() == 0 then + say("!bless") + schedule(2000, function() + if g_game.getClientVersion() > 1000 then + if not blessed and player:getBlessings() == 0 then + warn("!! Blessings not bought !!") + end + end + end) + end + end +end + + +addCheckBox("reUse", "Keep Crosshair", false, rightPanel, "Keep crosshair after using with item") +if true then + local excluded = {268, 237, 238, 23373, 266, 236, 239, 7643, 23375, 7642, 23374, 5908, 5942} + + onUseWith(function(pos, itemId, target, subType) + if settings.reUse and not table.find(excluded, itemId) then + schedule(50, function() + item = findItem(itemId) + if item then + modules.game_interface.startUseWith(item) + end + end) + end + end) +end + + +addCheckBox("suppliesControl", "TargetBot off if low supply", false, leftPanel, "Turn off TargetBot if either one of supply amount is below 50% of minimum.") +if true then + macro(500, function() + if not settings.suppliesControl then return end + if TargetBot.isOff() then return end + if CaveBot.isOff() then return end + if not hasSupplies() then + TargetBot.setOff() + end + end) +end + +addCheckBox("holdMwall", "Hold MW/WG", true, rightPanel, "Mark tiles with below hotkeys to automatically use Magic Wall or Wild Growth") +addTextEdit("holdMwHot", "Magic Wall Hotkey: ", "F5", rightPanel) +addTextEdit("holdWgHot", "Wild Growth Hotkey: ", "F6", rightPanel) +if true then + + local hold = 0 + local mwHot + local wgHot + + local candidates = {} + local m = macro(20, function() + mwHot = settings.holdMwHot + wgHot = settings.holdWgHot + + if not settings.holdMwall then return end + if #candidates == 0 then return end + + for i, pos in pairs(candidates) do + local tile = g_map.getTile(pos) + if tile then + if tile:getText():len() == 0 then + table.remove(candidates, i) + end + local rune = tile:getText() == "HOLD MW" and 3180 or tile:getText() == "HOLD WG" and 3156 + if tile:canShoot() and not isInPz() and tile:isWalkable() and tile:getTopUseThing():getId() ~= 2130 then + if math.abs(player:getPosition().x-tile:getPosition().x) < 8 and math.abs(player:getPosition().y-tile:getPosition().y) < 6 then + return useWith(rune, tile:getTopUseThing()) + end + end + end + end + end) + + onRemoveThing(function(tile, thing) + if not settings.holdMwall then return end + if thing:getId() ~= 2129 then return end + if tile:getText():find("HOLD") then + table.insert(candidates, tile:getPosition()) + local rune = tile:getText() == "HOLD MW" and 3180 or tile:getText() == "HOLD WG" and 3156 + if math.abs(player:getPosition().x-tile:getPosition().x) < 8 and math.abs(player:getPosition().y-tile:getPosition().y) < 6 then + return useWith(rune, tile:getTopUseThing()) + end + end + end) + + onAddThing(function(tile, thing) + if not settings.holdMwall then return end + if m.isOff() then return end + if thing:getId() ~= 2129 then return end + if tile:getText():len() > 0 then + table.remove(candidates, table.find(candidates,tile)) + end + end) + + onKeyDown(function(keys) + local wsadWalking = modules.game_walking.wsadWalking + if not wsadWalking then return end + if not settings.holdMwall then return end + if m.isOff() then return end + if keys ~= mwHot and keys ~= wgHot then return end + hold = now + + local tile = getTileUnderCursor() + if not tile then return end + + if tile:getText():len() > 0 then + tile:setText("") + else + if keys == mwHot then + tile:setText("HOLD MW") + else + tile:setText("HOLD WG") + end + table.insert(candidates, tile:getPosition()) + end + end) + + onKeyPress(function(keys) + local wsadWalking = modules.game_walking.wsadWalking + if not wsadWalking then return end + if not settings.holdMwall then return end + if m.isOff() then return end + if keys ~= mwHot and keys ~= wgHot then return end + + if (hold - now) < -1000 then + candidates = {} + for i, tile in ipairs(g_map.getTiles(posz())) do + local text = tile:getText() + if text:find("HOLD") then + tile:setText("") + end + end + end + end) +end + +addCheckBox("checkPlayer", "Check Players", true, rightPanel, "Auto look on players and mark level and vocation on character model") +if true then + local found + local function checkPlayers() + for i, spec in ipairs(getSpectators()) do + if spec:isPlayer() and spec:getText() == "" and spec:getPosition().z == posz() and spec ~= player then + g_game.look(spec) + found = now + end + end + end + if settings.checkPlayer then + schedule(500, function() + checkPlayers() + end) + end + + onPlayerPositionChange(function(x,y) + if not settings.checkPlayer then return end + if x.z ~= y.z then + schedule(20, function() checkPlayers() end) + end + end) + + onCreatureAppear(function(creature) + if not settings.checkPlayer then return end + if creature:isPlayer() and creature:getText() == "" and creature:getPosition().z == posz() and creature ~= player then + g_game.look(creature) + found = now + end + end) + + local regex = [[You see ([a-z 'A-z-]*) \(Level ([0-9]*)\)((?:.)* of the ([\w ]*),|)]] + onTextMessage(function(mode, text) + if not settings.checkPlayer then return end + + local re = regexMatch(text, regex) + if #re ~= 0 then + local name = re[1][2] + local level = re[1][3] + local guild = re[1][5] or "" + + if guild:len() > 10 then + guild = guild:sub(1,10) -- change to proper (last) values + guild = guild.."..." + end + local voc + if text:lower():find("sorcerer") then + voc = "MS" + elseif text:lower():find("druid") then + voc = "ED" + elseif text:lower():find("knight") then + voc = "EK" + elseif text:lower():find("paladin") then + voc = "RP" + end + local creature = getCreatureByName(name) + if creature then + creature:setText("\n"..level..voc.."\n"..guild) + end + if found and now - found < 500 then + modules.game_textmessage.clearMessages() + end + end + end) +end + +addCheckBox("nextBackpack", "Open Next Loot Container", true, leftPanel, "Auto open next loot container if full - has to have the same ID.") + local function openNextLootContainer() + if not settings.nextBackpack then return end + local containers = getContainers() + local lootCotaniersIds = CaveBot.GetLootContainers() + + for i, container in ipairs(containers) do + local cId = container:getContainerItem():getId() + if containerIsFull(container) then + if table.find(lootCotaniersIds, cId) then + for _, item in ipairs(container:getItems()) do + if item:getId() == cId then + return g_game.open(item, container) + end + end + end + end + end + end +if true then + onContainerOpen(function(container, previousContainer) + schedule(100, function() + openNextLootContainer() + end) + end) + + onAddItem(function(container, slot, item, oldItem) + schedule(100, function() + openNextLootContainer() + end) + end) +end + +addCheckBox("highlightTarget", "Highlight Current Target", true, rightPanel, "Additionaly hightlight current target with red glow") +if true then + local function forceMarked(creature) + if target() == creature then + creature:setMarked("red") + return schedule(333, function() forceMarked(creature) end) + end + end + + onAttackingCreatureChange(function(newCreature, oldCreature) + if not settings.highlightTarget then return end + if oldCreature then + oldCreature:setMarked('') + end + if newCreature then + forceMarked(newCreature) + end + end) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/extras.otui b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/extras.otui new file mode 100644 index 0000000..de551d9 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/extras.otui @@ -0,0 +1,158 @@ +ExtrasScrollBar < Panel + height: 28 + margin-top: 3 + + UIWidget + id: text + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-align: center + + HorizontalScrollBar + id: scroll + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 3 + minimum: 0 + maximum: 10 + step: 1 + +ExtrasTextEdit < Panel + height: 40 + margin-top: 7 + + UIWidget + id: text + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-align: center + + TextEdit + id: textEdit + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 0 + maximum: 10 + step: 1 + text-align: center + +ExtrasItem < Panel + height: 34 + margin-top: 7 + margin-left: 25 + margin-right: 25 + + UIWidget + id: text + anchors.left: parent.left + anchors.verticalCenter: next.verticalCenter + + BotItem + id: item + anchors.top: parent.top + anchors.right: parent.right + + +ExtrasCheckBox < BotSwitch + height: 20 + margin-top: 7 + +ExtrasWindow < MainWindow + !text: tr('Extras') + size: 440 360 + padding: 25 + + Label + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: parent.top + text-align: center + text: < CaveBot > + + Label + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: parent.top + text-align: center + text: < Miscellaneous > + + VerticalScrollBar + id: contentScroll + anchors.top: prev.bottom + margin-top: 3 + anchors.right: parent.right + anchors.bottom: separator.top + step: 28 + pixels-scroll: true + margin-right: -10 + margin-top: 5 + margin-bottom: 5 + + ScrollablePanel + id: content + anchors.top: prev.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: separator.top + vertical-scrollbar: contentScroll + margin-bottom: 10 + + Panel + id: left + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.horizontalCenter + margin-top: 5 + margin-left: 10 + margin-right: 10 + layout: + type: verticalBox + fit-children: true + + Panel + id: right + anchors.top: parent.top + anchors.left: parent.horizontalCenter + anchors.right: parent.right + margin-top: 5 + margin-left: 10 + margin-right: 10 + layout: + type: verticalBox + fit-children: true + + VerticalSeparator + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.horizontalCenter + + HorizontalSeparator + id: separator + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: closeButton.top + margin-bottom: 8 + + ResizeBorder + id: bottomResizeBorder + anchors.fill: separator + height: 3 + minimum: 260 + maximum: 600 + margin-left: 3 + margin-right: 3 + background: #ffffff88 + + Button + id: closeButton + !text: tr('Close') + font: cipsoftFont + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 45 21 + margin-right: 5 \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/hold_target.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/hold_target.lua new file mode 100644 index 0000000..484bf85 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/hold_target.lua @@ -0,0 +1,30 @@ +setDefaultTab("Tools") + +local targetID = nil + +-- escape when attacking will reset hold target +onKeyPress(function(keys) + if keys == "Escape" and targetID then + targetID = nil + end +end) + +macro(100, "Hold Target", function() + -- if attacking then save it as target, but check pos z in case of marking by mistake on other floor + if target() and target():getPosition().z == posz() and not target():isNpc() then + targetID = target():getId() + elseif not target() then + -- there is no saved data, do nothing + if not targetID then return end + + -- look for target + for i, spec in ipairs(getSpectators()) do + local sameFloor = spec:getPosition().z == posz() + local oldTarget = spec:getId() == targetID + + if sameFloor and oldTarget then + attack(spec) + end + end + end +end) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/ingame_editor.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/ingame_editor.lua new file mode 100644 index 0000000..1217b82 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/ingame_editor.lua @@ -0,0 +1,23 @@ +setDefaultTab("Tools") +-- allows to test/edit bot lua scripts ingame, you can have multiple scripts like this, just change storage.ingame_lua +UI.Button("Ingame script editor", function(newText) + UI.MultilineEditorWindow(storage.ingame_hotkeys or "", {title="Hotkeys editor", description="You can add your custom scrupts here"}, function(text) + storage.ingame_hotkeys = text + reload() + end) + end) + + UI.Separator() + + for _, scripts in pairs({storage.ingame_hotkeys}) do + if type(scripts) == "string" and scripts:len() > 3 then + local status, result = pcall(function() + assert(load(scripts, "ingame_editor"))() + end) + if not status then + error("Ingame edior error:\n" .. result) + end + end + end + + UI.Separator() \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/items.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/items.lua new file mode 100644 index 0000000..312aa37 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/items.lua @@ -0,0 +1,1404 @@ +LootItems = { + ["gold coin"] = 1, + ["platinum coin"] = 100, + ["crystal coin"] = 10000, + ["abyss hammer"] = 20000, + ["acorn"] = 10, + ["albino plate"] = 1500, + ["alloy legs"] = 11000, + ["alptramun's toothbrush"] = 270000, + ["amber"] = 20000, + ["amber staff"] = 8000, + ["amber with a bug"] = 41000, + ["amber with a dragonfly"] = 56000, + ["ancient amulet"] = 200, + ["ancient belt buckle"] = 260, + ["ancient coin"] = 350, + ["ancient liche bone"] = 28000, + ["ancient shield"] = 900, + ["ancient stone"] = 200, + ["angel figurine"] = 36000, + ["angelic axe"] = 5000, + ["ankh"] = 100, + ["antlers"] = 50, + ["ape fur"] = 120, + ["apron"] = 1300, + ["arbalest"] = 42000, + ["arcane staff"] = 42000, + ["assassin dagger"] = 20000, + ["axe"] = 7, + ["axe ring"] = 100, + ["baby seal doll"] = 20000, + ["badger boots"] = 7500, + ["badger fur"] = 15, + ["bamboo stick"] = 30, + ["banana sash"] = 55, + ["banana staff"] = 1000, + ["bandana"] = 150, + ["bar of gold"] = 10000, + ["basalt fetish"] = 210, + ["basalt figurine"] = 160, + ["bast skirt"] = 750, + ["bat decoration"] = 2000, + ["bat wing"] = 50, + ["battle axe"] = 80, + ["battle hammer"] = 120, + ["battle shield"] = 95, + ["battle stone"] = 290, + ["batwing hat"] = 8000, + ["bear paw"] = 100, + ["beast's nightmare-cushion"] = 630000, + ["beastslayer axe"] = 1500, + ["bed of nails"] = 500, + ["beer tap"] = 50, + ["beetle carapace"] = 200, + ["beetle necklace"] = 1500, + ["behemoth claw"] = 2000, + ["behemoth trophy"] = 20000, + ["bejeweled ship's telescope"] = 20000, + ["berserk potion"] = 500, + ["berserker"] = 40000, + ["black hood"] = 190, + ["black pearl"] = 280, + ["black shield"] = 800, + ["black wool"] = 300, + ["blacksteel sword"] = 6000, + ["blade of corruption"] = 60000, + ["blazing bone"] = 610, + ["blessed sceptre"] = 40000, + ["blood preservation"] = 320, + ["blood tincture in a vial"] = 360, + ["bloody dwarven beard"] = 110, + ["bloody edge"] = 30000, + ["bloody pincers"] = 100, + ["bloody tears"] = 70000, + ["blue crystal shard"] = 1500, + ["blue crystal splinter"] = 400, + ["blue gem"] = 5000, + ["blue glass plate"] = 60, + ["blue goanna scale"] = 230, + ["blue legs"] = 15000, + ["blue piece of cloth"] = 200, + ["blue robe"] = 10000, + ["blue rose"] = 250, + ["boggy dreads"] = 200, + ["bola"] = 35, + ["bone club"] = 5, + ["bone fetish"] = 150, + ["bone shield"] = 80, + ["bone shoulderplate"] = 150, + ["bone sword"] = 20, + ["bone toothpick"] = 150, + ["bonebeast trophy3"] = 6000, + ["bonebreaker"] = 10000, + ["bonecarving knife"] = 190, + ["bonelord eye"] = 80, + ["bonelord helmet"] = 2200, + ["bonelord shield"] = 1200, + ["bones of zorvorax"] = 10000, + ["bony tail"] = 210, + ["book of necromantic rituals"] = 180, + ["book of prayers"] = 120, + ["book page"] = 640, + ["boots of haste"] = 30000, + ["bow"] = 100, + ["bowl of terror sweat"] = 500, + ["brain head's giant neuron"] = 100000, + ["brain head's left hemisphere"] = 90000, + ["brain head's right hemisphere"] = 50000, + ["brass armor"] = 150, + ["brass helmet"] = 30, + ["brass legs"] = 49, + ["brass shield"] = 25, + ["bright bell"] = 220, + ["bright sword"] = 6000, + ["brimstone fangs"] = 380, + ["brimstone shell"] = 210, + ["broadsword"] = 500, + ["broken crossbow"] = 30, + ["broken draken mail"] = 340, + ["broken gladiator shield"] = 190, + ["broken halberd"] = 100, + ["broken helmet"] = 20, + ["broken key ring"] = 8000, + ["broken longbow"] = 120, + ["broken ring of ending"] = 4000, + ["broken shamanic staff"] = 35, + ["broken slicer"] = 120, + ["broken throwing axe"] = 230, + ["broken visor"] = 1900, + ["bronze amulet"] = 50, + ["brooch of embracement"] = 14000, + ["brown crystal splinter"] = 400, + ["brown piece of cloth"] = 100, + ["brutetamer's staff"] = 1500, + ["buckle"] = 7000, + ["bullseye potion"] = 500, + ["bunch of ripe rice"] = 75, + ["bunch of troll hair"] = 30, + ["bundle of cursed straw"] = 800, + ["butcher's axe"] = 18000, + ["calopteryx cape"] = 15000, + ["capricious heart"] = 2100, + ["capricious robe"] = 1200, + ["carapace shield"] = 32000, + ["carlin sword"] = 118, + ["carniphila seeds"] = 50, + ["carrion worm fang"] = 35, + ["castle shield"] = 5000, + ["cat's paw"] = 2000, + ["cave devourer eyes"] = 550, + ["cave devourer legs"] = 350, + ["cave devourer maw"] = 600, + ["cavebear skull"] = 550, + ["centipede leg"] = 28, + ["ceremonial ankh"] = 20000, + ["chain armor"] = 70, + ["chain bolter"] = 40000, + ["chain helmet"] = 17, + ["chain legs"] = 25, + ["chaos mace"] = 9000, + ["charmer's tiara"] = 900, + ["chasm spawn abdomen"] = 240, + ["chasm spawn head"] = 850, + ["chasm spawn tail"] = 120, + ["cheese cutter"] = 50, + ["cheesy figurine"] = 150, + ["chicken feather"] = 30, + ["chitinous mouth"] = 10000, + ["claw of 'the noxious spawn'"] = 15000, + ["cliff strider claw"] = 800, + ["closed trap"] = 75, + ["club"] = 1, + ["club ring"] = 100, + ["coal"] = 20, + ["coat"] = 1, + ["cobra crest"] = 650, + ["cobra crown"] = 50000, + ["cobra tongue"] = 15, + ["coconut shoes"] = 500, + ["collar of blue plasma"] = 6000, + ["collar of green plasma"] = 6000, + ["collar of red plasma"] = 6000, + ["colourful feather"] = 110, + ["colourful feathers"] = 400, + ["colourful snail shell"] = 250, + ["compass"] = 45, + ["composite hornbow"] = 25000, + ["compound eye"] = 150, + ["condensed energy"] = 260, + ["copper shield"] = 50, + ["coral brooch"] = 750, + ["corrupted flag"] = 700, + ["countess sorrow's frozen tear"] = 50000, + ["cow bell"] = 120, + ["cowtana"] = 2500, + ["crab pincers"] = 35, + ["cracked alabaster vase"] = 180, + ["cranial basher"] = 30000, + ["crawler head plating"] = 210, + ["crawler's essence"] = 3700, + ["crest of the deep seas"] = 10000, + ["crocodile boots"] = 1000, + ["crossbow"] = 120, + ["crowbar"] = 50, + ["crown"] = 2700, + ["crown armor"] = 12000, + ["crown helmet"] = 2500, + ["crown legs"] = 12000, + ["crown shield"] = 8000, + ["cruelty's chest"] = 720000, + ["cruelty's claw"] = 640000, + ["crunor idol"] = 30000, + ["crusader helmet"] = 6000, + ["crystal bone"] = 250, + ["crystal crossbow"] = 35000, + ["crystal mace"] = 12000, + ["crystal necklace"] = 400, + ["crystal of balance"] = 1000, + ["crystal of focus"] = 2000, + ["crystal of power"] = 3000, + ["crystal pedestal"] = 500, + ["crystal ring"] = 250, + ["crystal sword"] = 600, + ["crystal wand"] = 10000, + ["crystalline armor"] = 16000, + ["crystalline spikes"] = 440, + ["crystallized anger"] = 400, + ["cultish mask"] = 280, + ["cultish robe"] = 150, + ["cultish symbol"] = 500, + ["curious matter"] = 430, + ["cursed bone"] = 6000, + ["cursed shoulder spikes"] = 320, + ["cyan crystal fragment"] = 800, + ["cyclops toe"] = 55, + ["cyclops trophy"] = 500, + ["dagger"] = 2, + ["damaged armor plates"] = 280, + ["damaged worm head"] = 8000, + ["damselfly eye"] = 25, + ["damselfly wing"] = 20, + ["dandelion seeds"] = 200, + ["dangerous proto matter"] = 300, + ["daramian mace"] = 110, + ["daramian waraxe"] = 1000, + ["dark armor"] = 400, + ["dark bell"] = 250, + ["dark helmet"] = 250, + ["dark mushroom"] = 100, + ["dark rosary"] = 48, + ["dark shield"] = 400, + ["dead rat"] = 1, + ["dead weight"] = 450, + ["death ring"] = 1000, + ["deepling axe"] = 40000, + ["deepling breaktime snack"] = 90, + ["deepling claw"] = 430, + ["deepling guard belt buckle"] = 230, + ["deepling ridge"] = 360, + ["deepling scales"] = 80, + ["deepling squelcher"] = 7000, + ["deepling staff"] = 4000, + ["deepling warts"] = 180, + ["deeptags"] = 290, + ["deepworm jaws"] = 500, + ["deepworm spike roots"] = 650, + ["deepworm spikes"] = 800, + ["deer trophy3"] = 3000, + ["demon dust"] = 300, + ["demon helmet"] = 40000, + ["demon horn"] = 1000, + ["demon shield"] = 30000, + ["demon trophy"] = 40000, + ["demonbone amulet"] = 32000, + ["demonic essence"] = 1000, + ["demonic finger"] = 1000, + ["demonic skeletal hand"] = 80, + ["demonrage sword"] = 36000, + ["depth calcei"] = 25000, + ["depth galea"] = 35000, + ["depth lorica"] = 30000, + ["depth ocrea"] = 16000, + ["depth scutum"] = 36000, + ["devil helmet"] = 1000, + ["diabolic skull"] = 19000, + ["diamond"] = 15000, + ["diamond sceptre"] = 3000, + ["diremaw brainpan"] = 350, + ["diremaw legs"] = 270, + ["dirty turban"] = 120, + ["disgusting trophy"] = 3000, + ["distorted heart"] = 2100, + ["distorted robe"] = 1200, + ["divine plate"] = 55000, + ["djinn blade"] = 15000, + ["doll"] = 200, + ["double axe"] = 260, + ["doublet"] = 3, + ["downy feather"] = 20, + ["dowser"] = 35, + ["drachaku"] = 10000, + ["dracola's eye"] = 50000, + ["dracoyle statue"] = 5000, + ["dragon blood"] = 700, + ["dragon claw"] = 8000, + ["dragon figurine"] = 45000, + ["dragon hammer"] = 2000, + ["dragon lance"] = 9000, + ["dragon lord trophy"] = 10000, + ["dragon necklace"] = 100, + ["dragon priest's wandtip"] = 175, + ["dragon robe"] = 50000, + ["dragon scale mail"] = 40000, + ["dragon shield"] = 4000, + ["dragon slayer"] = 15000, + ["dragon tongue"] = 550, + ["dragonbone staff"] = 3000, + ["dragon's tail"] = 100, + ["draken boots"] = 40000, + ["draken sulphur"] = 550, + ["draken trophy"] = 15000, + ["draken wristbands"] = 430, + ["drakinata"] = 10000, + ["draptor scales"] = 800, + ["dreaded cleaver"] = 10000, + ["dream essence egg"] = 205, + ["dung ball"] = 130, + ["dwarven armor"] = 30000, + ["dwarven axe"] = 1500, + ["dwarven legs"] = 40000, + ["dwarven ring"] = 100, + ["dwarven shield"] = 100, + ["earflap"] = 40, + ["earth blacksteel sword"] = 6000, + ["earth cranial basher"] = 30000, + ["earth crystal mace"] = 12000, + ["earth dragon slayer"] = 15000, + ["earth headchopper"] = 6000, + ["earth heroic axe"] = 30000, + ["earth knight axe"] = 2000, + ["earth mystic blade"] = 30000, + ["earth orcish maul"] = 6000, + ["earth relic sword"] = 25000, + ["earth spike sword"] = 1000, + ["earth war axe"] = 12000, + ["earth war hammer"] = 1200, + ["ectoplasmic sushi"] = 300, + ["egg of the many"] = 15000, + ["elder bonelord tentacle"] = 150, + ["elite draken mail"] = 50000, + ["elven amulet"] = 100, + ["elven astral observer"] = 90, + ["elven hoof"] = 115, + ["elven scouting glass"] = 50, + ["elvish bow"] = 2000, + ["elvish talisman"] = 45, + ["emerald bangle"] = 800, + ["empty honey glass"] = 270, + ["empty potion flask"] = 5, + ["enchanted chicken wing"] = 20000, + ["energy ball"] = 300, + ["energy blacksteel sword"] = 6000, + ["energy cranial basher"] = 30000, + ["energy crystal mace"] = 12000, + ["energy dragon slayer"] = 15000, + ["energy headchopper"] = 6000, + ["energy heroic axe"] = 30000, + ["energy knight axe"] = 2000, + ["energy mystic blade"] = 30000, + ["energy orcish maul"] = 6000, + ["energy relic sword"] = 25000, + ["energy ring"] = 100, + ["energy spike sword"] = 1000, + ["energy vein"] = 270, + ["energy war axe"] = 12000, + ["energy war hammer"] = 1200, + ["ensouled essence"] = 820, + ["epee"] = 8000, + ["essence of a bad dream"] = 360, + ["ethno coat"] = 200, + ["execowtioner axe"] = 12000, + ["executioner"] = 55000, + ["explorer brooch"] = 50, + ["eye of a deepling"] = 150, + ["eye of a weeper"] = 650, + ["eye of corruption"] = 390, + ["fafnar symbol"] = 950, + ["fairy wings"] = 200, + ["falcon crest"] = 650, + ["feather headdress"] = 850, + ["fern"] = 20, + ["fiery blacksteel sword"] = 6000, + ["fiery cranial basher"] = 30000, + ["fiery crystal mace"] = 12000, + ["fiery dragon slayer"] = 15000, + ["fiery headchopper"] = 6000, + ["fiery heart"] = 375, + ["fiery heroic axe"] = 30000, + ["fiery knight axe"] = 2000, + ["fiery mystic blade"] = 30000, + ["fiery orcish maul"] = 6000, + ["fiery relic sword"] = 25000, + ["fiery spike sword"] = 1000, + ["fiery war axe"] = 12000, + ["fiery war hammer"] = 1200, + ["fig leaf"] = 200, + ["figurine of cruelty"] = 3100000, + ["figurine of greed"] = 2900000, + ["figurine of hatred"] = 2700000, + ["figurine of malice"] = 2800000, + ["figurine of megalomania"] = 5000000, + ["figurine of spite"] = 3000000, + ["fir cone"] = 25, + ["fire axe"] = 8000, + ["fire mushroom"] = 200, + ["fire sword"] = 1000, + ["fish fin"] = 150, + ["fishing rod"] = 40, + ["flask of embalming fluid"] = 30, + ["flask of warrior's sweat"] = 10000, + ["flintstone"] = 800, + ["flower dress"] = 1000, + ["flower wreath"] = 500, + ["focus cape"] = 6000, + ["fox paw"] = 100, + ["frazzle skin"] = 400, + ["frazzle tongue"] = 700, + ["frost giant pelt"] = 160, + ["frosty ear of a troll"] = 30, + ["frosty heart"] = 280, + ["frozen lightning"] = 270, + ["frozen starlight"] = 20000, + ["fur armor"] = 5000, + ["fur boots"] = 2000, + ["fur shred"] = 200, + ["furry club"] = 1000, + ["garlic necklace"] = 50, + ["gauze bandage"] = 90, + ["gear crystal"] = 200, + ["gear wheel"] = 500, + ["gearwheel chain"] = 5000, + ["gemmed figurine"] = 3500, + ["geomancer's robe"] = 80, + ["geomancer's staff"] = 120, + ["ghastly dragon head"] = 700, + ["ghostly tissue"] = 90, + ["ghoul snack"] = 60, + ["giant amethyst"] = 60000, + ["giant crab pincer"] = 950, + ["giant emerald"] = 90000, + ["giant eye"] = 380, + ["giant ruby"] = 70000, + ["giant sapphire"] = 50000, + ["giant shimmering pearl"] = 3000, + ["giant smithhammer"] = 250, + ["giant sword"] = 17000, + ["giant tentacle"] = 10000, + ["giant topaz"] = 80000, + ["girlish hair decoration"] = 30, + ["glacial rod"] = 6500, + ["glacier amulet"] = 1500, + ["glacier kilt"] = 11000, + ["glacier mask"] = 2500, + ["glacier robe"] = 11000, + ["glacier shoes"] = 2500, + ["gland"] = 500, + ["glistening bone"] = 250, + ["glob of acid slime"] = 25, + ["glob of mercury"] = 20, + ["glob of tar"] = 30, + ["gloom wolf fur"] = 70, + ["glooth amulet"] = 2000, + ["glooth axe"] = 1500, + ["glooth blade"] = 1500, + ["glooth cape"] = 7000, + ["glooth club"] = 1500, + ["glooth whip"] = 2500, + ["glorious axe"] = 3000, + ["glowing rune"] = 350, + ["goanna claw"] = 260, + ["goanna meat"] = 190, + ["goat grass"] = 50, + ["goblet of gloom"] = 12000, + ["goblin ear"] = 20, + ["gold ingot"] = 5000, + ["gold nugget"] = 850, + ["gold ring"] = 8000, + ["golden amulet"] = 2000, + ["golden armor"] = 20000, + ["golden brush"] = 250, + ["golden fafnar trophy"] = 10000, + ["golden figurine"] = 3000, + ["golden legs"] = 30000, + ["golden lotus brooch"] = 270, + ["golden mask"] = 38000, + ["golden mug"] = 250, + ["golden sickle"] = 1000, + ["goo shell"] = 4000, + ["goosebump leather"] = 650, + ["grant of arms"] = 950, + ["grasshopper legs"] = 15000, + ["grave flower"] = 25, + ["greed's arm"] = 950000, + ["green bandage"] = 180, + ["green crystal fragment"] = 800, + ["green crystal shard"] = 1500, + ["green crystal splinter"] = 400, + ["green dragon leather"] = 100, + ["green dragon scale"] = 100, + ["green gem"] = 5000, + ["green glass plate"] = 180, + ["green mushroom"] = 100, + ["green piece of cloth"] = 200, + ["greenwood coat"] = 50000, + ["griffin shield"] = 3000, + ["grimace"] = 120000, + ["gruesome fan"] = 15000, + ["guardian axe"] = 9000, + ["guardian boots"] = 35000, + ["guardian halberd"] = 11000, + ["guardian shield"] = 2000, + ["guidebook"] = 200, + ["hailstorm rod"] = 3000, + ["hair of a banshee"] = 350, + ["halberd"] = 400, + ["half-digested piece of meat"] = 55, + ["half-digested stones"] = 40, + ["half-eaten brain"] = 85, + ["ham"] = 4, + ["hammer of wrath"] = 30000, + ["hand"] = 1450, + ["hand axe"] = 4, + ["hardened bone"] = 70, + ["harpoon of a giant snail"] = 15000, + ["hatched rorc egg"] = 30, + ["hatchet"] = 25, + ["haunted blade"] = 8000, + ["haunted piece of wood"] = 115, + ["hazardous heart"] = 5000, + ["hazardous robe"] = 3000, + ["head"] = 3500, + ["headchopper"] = 6000, + ["heat core"] = 10000, + ["heaven blossom"] = 50, + ["heavy mace"] = 50000, + ["heavy machete"] = 90, + ["heavy trident"] = 2000, + ["hellhound slobber"] = 500, + ["hellspawn tail"] = 475, + ["helmet of the lost"] = 2000, + ["hemp rope"] = 350, + ["heroic axe"] = 30000, + ["hexagonal ruby"] = 30000, + ["hibiscus dress"] = 3000, + ["hideous chunk"] = 510, + ["hieroglyph banner"] = 500, + ["high guard flag"] = 550, + ["high guard shoulderplates"] = 130, + ["hive bow"] = 28000, + ["hive scythe"] = 17000, + ["hollow stampor hoof"] = 400, + ["holy ash"] = 160, + ["holy orchid"] = 90, + ["honeycomb"] = 40, + ["horn"] = 300, + ["horn of kalyassa"] = 10000, + ["horoscope"] = 40, + ["horseman helmet"] = 280, + ["huge chunk of crude iron"] = 15000, + ["huge shell"] = 15000, + ["huge spiky snail shell"] = 8000, + ["humongous chunk"] = 540, + ["hunter's quiver"] = 80, + ["hunting spear"] = 25, + ["hydra egg"] = 500, + ["hydra head"] = 600, + ["ice flower"] = 370, + ["ice rapier"] = 1000, + ["icy blacksteel sword"] = 6000, + ["icy cranial basher"] = 30000, + ["icy crystal mace"] = 12000, + ["icy dragon slayer"] = 15000, + ["icy headchopper"] = 6000, + ["icy heroic axe"] = 30000, + ["icy knight axe"] = 2000, + ["icy mystic blade"] = 30000, + ["icy orcish maul"] = 6000, + ["icy relic sword"] = 25000, + ["icy spike sword"] = 1000, + ["icy war axe"] = 12000, + ["icy war hammer"] = 1200, + ["incantation notes"] = 90, + ["infernal heart"] = 2100, + ["infernal robe"] = 1200, + ["inkwell"] = 8, + ["instable proto matter"] = 300, + ["iron helmet"] = 150, + ["iron ore"] = 500, + ["ivory carving"] = 300, + ["ivory comb"] = 8000, + ["izcandar's snow globe"] = 180000, + ["izcandar's sundial"] = 225000, + ["jacket"] = 1, + ["jade hammer"] = 25000, + ["jade hat"] = 9000, + ["jagged sickle"] = 150000, + ["jaws"] = 3900, + ["jewelled belt"] = 180, + ["katana"] = 35, + ["katex' blood"] = 210, + ["key to the drowned library"] = 330, + ["knight armor"] = 5000, + ["knight axe"] = 2000, + ["knight legs"] = 5000, + ["kollos shell"] = 420, + ["kongra's shoulderpad"] = 100, + ["krimhorn helmet"] = 200, + ["lamassu hoof"] = 330, + ["lamassu horn"] = 240, + ["lancer beetle shell"] = 80, + ["lancet"] = 90, + ["lavos armor"] = 16000, + ["leaf legs"] = 500, + ["leather armor"] = 12, + ["leather boots"] = 2, + ["leather harness"] = 750, + ["leather helmet"] = 4, + ["leather legs"] = 9, + ["legion helmet"] = 22, + ["legionnaire flags"] = 500, + ["leopard armor"] = 300, + ["leviathan's amulet"] = 3000, + ["life crystal"] = 50, + ["life preserver"] = 300, + ["life ring"] = 50, + ["light shovel"] = 300, + ["lightning boots"] = 2500, + ["lightning headband"] = 2500, + ["lightning legs"] = 11000, + ["lightning pendant"] = 1500, + ["lightning robe"] = 11000, + ["lion cloak patch"] = 190, + ["lion crest"] = 270, + ["lion figurine"] = 10000, + ["lion seal"] = 210, + ["lion trophy3"] = 3000, + ["lion's mane"] = 60, + ["little bowl of myrrh"] = 500, + ["lizard essence"] = 300, + ["lizard heart"] = 530, + ["lizard leather"] = 150, + ["lizard scale"] = 120, + ["lizard trophy"] = 8000, + ["longing eyes"] = 8000, + ["longsword"] = 51, + ["lost basher's spike"] = 280, + ["lost bracers"] = 140, + ["lost husher's staff"] = 250, + ["lost soul"] = 120, + ["luminescent crystal pickaxe"] = 50, + ["luminous orb"] = 1000, + ["lump of dirt"] = 10, + ["lump of earth"] = 130, + ["lunar staff"] = 5000, + ["mace"] = 30, + ["machete"] = 6, + ["mad froth"] = 80, + ["magic light wand"] = 35, + ["magic plate armor"] = 90000, + ["magic sulphur"] = 8000, + ["magma amulet"] = 1500, + ["magma boots"] = 2500, + ["magma clump"] = 570, + ["magma coat"] = 11000, + ["magma legs"] = 11000, + ["magma monocle"] = 2500, + ["malice's horn"] = 620000, + ["malice's spine"] = 850000, + ["malofur's lunchbox"] = 240000, + ["mammoth fur cape"] = 6000, + ["mammoth fur shorts"] = 850, + ["mammoth tusk"] = 100, + ["mammoth whopper"] = 300, + ["mantassin tail"] = 280, + ["manticore ear"] = 310, + ["manticore tail"] = 220, + ["marlin trophy"] = 5000, + ["marsh stalker beak"] = 65, + ["marsh stalker feather"] = 50, + ["mastermind potion"] = 500, + ["mastermind shield"] = 50000, + ["maxilla"] = 250, + ["maxxenius head"] = 500000, + ["meat"] = 2, + ["meat hammer"] = 60, + ["medal of valiance"] = 410000, + ["medusa shield"] = 9000, + ["megalomania's essence"] = 1900000, + ["megalomania's skull"] = 1500000, + ["mercenary sword"] = 12000, + ["metal bat"] = 9000, + ["metal spats"] = 2000, + ["metal spike"] = 320, + ["might ring"] = 250, + ["milk churn"] = 100, + ["mind stone"] = 100, + ["mino lance"] = 7000, + ["mino shield"] = 3000, + ["minotaur horn"] = 75, + ["minotaur leather"] = 80, + ["minotaur trophy"] = 500, + ["miraculum"] = 60, + ["mirror"] = 10, + ["model ship"] = 1000, + ["modified crossbow"] = 10000, + ["mooh'tah plate"] = 6000, + ["moohtant cudgel"] = 14000, + ["moonlight rod"] = 200, + ["moonstone"] = 13000, + ["morbid tapestry"] = 30000, + ["morgaroth's heart"] = 15000, + ["morning star"] = 100, + ["mould heart"] = 2100, + ["mould robe"] = 1200, + ["mr. punish's handcuffs"] = 50000, + ["muck rod"] = 6000, + ["mucus plug"] = 500, + ["mutated bat ear"] = 420, + ["mutated flesh"] = 50, + ["mutated rat tail"] = 150, + ["mycological bow"] = 35000, + ["mysterious fetish"] = 50, + ["mystic blade"] = 30000, + ["mystic turban"] = 150, + ["mystical hourglass"] = 700, + ["naginata"] = 2000, + ["necklace of the deep"] = 3000, + ["necromantic robe"] = 250, + ["necrotic rod"] = 1000, + ["nettle blossom"] = 75, + ["nettle spit"] = 25, + ["nightmare blade"] = 35000, + ["noble amulet"] = 430000, + ["noble armor"] = 900, + ["noble axe"] = 10000, + ["noble cape"] = 425000, + ["noble turban"] = 430, + ["norse shield"] = 1500, + ["northwind rod"] = 1500, + ["nose ring"] = 2000, + ["obsidian lance"] = 500, + ["odd organ"] = 410, + ["ogre ear stud"] = 180, + ["ogre nose ring"] = 210, + ["old parchment"] = 500, + ["onyx chip"] = 500, + ["onyx flail"] = 22000, + ["onyx pendant"] = 3500, + ["opal"] = 500, + ["orange mushroom"] = 150, + ["orb"] = 750, + ["orc leather"] = 30, + ["orc tooth"] = 150, + ["orc trophy3"] = 1000, + ["orcish axe"] = 350, + ["orcish gear"] = 85, + ["orcish maul"] = 6000, + ["orichalcum pearl"] = 40, + ["oriental shoes"] = 15000, + ["ornamented axe"] = 20000, + ["ornamented shield"] = 1500, + ["ornate chestplate"] = 60000, + ["ornate crossbow"] = 12000, + ["ornate legs"] = 40000, + ["ornate locket"] = 18000, + ["ornate mace"] = 42000, + ["ornate shield"] = 42000, + ["orshabaal's brain"] = 12000, + ["pair of hellflayer horns"] = 1300, + ["pair of iron fists"] = 4000, + ["pair of old bracers"] = 500, + ["paladin armor"] = 15000, + ["pale worm's scalp"] = 489000, + ["panda teddy"] = 30000, + ["panther head"] = 750, + ["panther paw"] = 300, + ["patch of fine cloth"] = 1350, + ["patched boots"] = 2000, + ["peacock feather fan"] = 350, + ["pelvis bone"] = 30, + ["perfect behemoth fang"] = 250, + ["pet pig"] = 1500, + ["petrified scream"] = 250, + ["phantasmal hair"] = 500, + ["pharaoh banner"] = 1000, + ["pharaoh sword"] = 23000, + ["phoenix shield"] = 16000, + ["pick"] = 15, + ["piece of archer armor"] = 20, + ["piece of crocodile leather"] = 15, + ["piece of dead brain"] = 420, + ["piece of draconian steel"] = 3000, + ["piece of hell steel"] = 500, + ["piece of hellfire armor"] = 550, + ["piece of massacre's shell"] = 50000, + ["piece of royal steel"] = 10000, + ["piece of scarab shell"] = 45, + ["piece of swampling wood"] = 30, + ["piece of warrior armor"] = 50, + ["pieces of magic chalk"] = 210, + ["pig foot"] = 10, + ["pile of grave earth"] = 25, + ["pirate boots"] = 3000, + ["pirate hat"] = 1000, + ["pirate knee breeches"] = 200, + ["pirate shirt"] = 500, + ["pirate voodoo doll"] = 500, + ["plagueroot offshoot"] = 280000, + ["plasma pearls"] = 250, + ["plasmatic lightning"] = 270, + ["plate armor"] = 400, + ["plate legs"] = 115, + ["plate shield"] = 45, + ["platinum amulet"] = 2500, + ["poison dagger"] = 50, + ["poison gland"] = 210, + ["poison spider shell"] = 10, + ["poisonous slime"] = 50, + ["polar bear paw"] = 30, + ["pool of chitinous glue"] = 480, + ["porcelain mask"] = 2000, + ["powder herb"] = 10, + ["power ring"] = 50, + ["prismatic quartz"] = 450, + ["pristine worm head"] = 15000, + ["protection amulet"] = 100, + ["protective charm"] = 60, + ["pulverized ore"] = 400, + ["purified soul"] = 530, + ["purple robe"] = 110, + ["purple tome"] = 2000, + ["quara bone"] = 500, + ["quara eye"] = 350, + ["quara pincers"] = 410, + ["quara tentacle"] = 140, + ["queen's sceptre"] = 20000, + ["quill"] = 1100, + ["rabbit's foot"] = 50, + ["ragnir helmet"] = 400, + ["rainbow quartz"] = 500, + ["rapier"] = 5, + ["rare earth"] = 80, + ["ratana"] = 500, + ["ravenous circlet"] = 220000, + ["red crystal fragment"] = 800, + ["red dragon leather"] = 200, + ["red dragon scale"] = 200, + ["red gem"] = 1000, + ["red goanna scale"] = 270, + ["red hair dye"] = 40, + ["red lantern"] = 250, + ["red piece of cloth"] = 300, + ["red tome"] = 2000, + ["relic sword"] = 25000, + ["rhino hide"] = 175, + ["rhino horn"] = 265, + ["rhino horn carving"] = 300, + ["rift bow"] = 45000, + ["rift crossbow"] = 45000, + ["rift lance"] = 30000, + ["rift shield"] = 50000, + ["ring of blue plasma"] = 8000, + ["ring of green plasma"] = 8000, + ["ring of healing"] = 100, + ["ring of red plasma"] = 8000, + ["ring of the sky"] = 30000, + ["ripper lance"] = 500, + ["rod"] = 2200, + ["roots"] = 1200, + ["rope"] = 15, + ["rope belt"] = 66, + ["rorc egg"] = 120, + ["rorc feather"] = 70, + ["rotten heart"] = 74000, + ["rotten piece of cloth"] = 30, + ["royal axe"] = 40000, + ["royal helmet"] = 30000, + ["royal tapestry"] = 1000, + ["rubber cap"] = 11000, + ["ruby necklace"] = 2000, + ["runed sword"] = 45000, + ["ruthless axe"] = 45000, + ["sabre"] = 12, + ["sabretooth"] = 400, + ["sacred tree amulet"] = 3000, + ["safety pin"] = 120, + ["sai"] = 16500, + ["salamander shield"] = 280, + ["sample of monster blood"] = 250, + ["sandcrawler shell"] = 20, + ["sapphire hammer"] = 7000, + ["scale armor"] = 75, + ["scale of corruption"] = 680, + ["scale of gelidrazah"] = 10000, + ["scarab amulet"] = 200, + ["scarab pincers"] = 280, + ["scarab shield"] = 2000, + ["scimitar"] = 150, + ["scorpion tail"] = 25, + ["scroll of heroic deeds"] = 230, + ["scythe"] = 10, + ["scythe leg"] = 450, + ["sea horse figurine"] = 42000, + ["sea serpent scale"] = 520, + ["sea serpent trophy"] = 10000, + ["seeds"] = 150, + ["sentinel shield"] = 120, + ["serpent sword"] = 900, + ["shadow herb"] = 20, + ["shadow sceptre"] = 10000, + ["shaggy tail"] = 25, + ["shamanic hood"] = 45, + ["shamanic talisman"] = 200, + ["shard"] = 2000, + ["shimmering beetles"] = 150, + ["shiny stone"] = 500, + ["shockwave amulet"] = 3000, + ["short sword"] = 10, + ["shovel"] = 8, + ["sickle"] = 3, + ["sight of surrender's eye"] = 3000, + ["signet ring"] = 480000, + ["silencer claws"] = 390, + ["silencer resonating chamber"] = 600, + ["silken bookmark"] = 1300, + ["silkweaver bow"] = 12000, + ["silky fur"] = 35, + ["silver amulet"] = 50, + ["silver brooch"] = 150, + ["silver dagger"] = 500, + ["silver fafnar trophy"] = 1000, + ["silver hand mirror"] = 10000, + ["simple dress"] = 50, + ["single human eye"] = 1000, + ["skeleton decoration"] = 3000, + ["skull belt"] = 80, + ["skull coin"] = 12000, + ["skull fetish"] = 250, + ["skull helmet"] = 40000, + ["skull of ratha"] = 250, + ["skull shatterer"] = 170, + ["skull staff"] = 6000, + ["skullcracker armor"] = 18000, + ["skunk tail"] = 50, + ["slime mould"] = 175, + ["slimy leg"] = 4500, + ["sling herb"] = 10, + ["small amethyst"] = 200, + ["small axe"] = 5, + ["small diamond"] = 300, + ["small emerald"] = 250, + ["small enchanted amethyst"] = 200, + ["small enchanted emerald"] = 250, + ["small enchanted ruby"] = 250, + ["small enchanted sapphire"] = 250, + ["small energy ball"] = 250, + ["small flask of eyedrops"] = 95, + ["small notebook"] = 480, + ["small oil lamp"] = 150, + ["small pitchfork"] = 70, + ["small ruby"] = 250, + ["small sapphire"] = 250, + ["small topaz"] = 200, + ["snake skin"] = 400, + ["snakebite rod"] = 100, + ["sniper gloves"] = 2000, + ["soldier helmet"] = 16, + ["solid rage"] = 310, + ["some grimeleech wings"] = 1200, + ["soul orb"] = 25, + ["soul stone"] = 6000, + ["souleater trophy"] = 7500, + ["spark sphere"] = 350, + ["sparkion claw"] = 290, + ["sparkion legs"] = 310, + ["sparkion stings"] = 280, + ["sparkion tail"] = 300, + ["spear"] = 3, + ["spectral gold nugget"] = 500, + ["spectral silver nugget"] = 250, + ["spellsinger's seal"] = 280, + ["spellweaver's robe"] = 12000, + ["sphinx feather"] = 470, + ["sphinx tiara"] = 360, + ["spider fangs"] = 10, + ["spider silk"] = 100, + ["spidris mandible"] = 450, + ["spike shield"] = 250, + ["spike sword"] = 240, + ["spiked iron ball"] = 100, + ["spiked squelcher"] = 5000, + ["spiky club"] = 300, + ["spirit cloak"] = 350, + ["spirit container"] = 40000, + ["spite's spirit"] = 840000, + ["spitter nose"] = 340, + ["spooky blue eye"] = 95, + ["spool of yarn"] = 1000, + ["springsprout rod"] = 3600, + ["srezz' eye"] = 300, + ["stampor horn"] = 280, + ["stampor talons"] = 150, + ["star amulet"] = 500, + ["star herb"] = 15, + ["statue of abyssador"] = 4000, + ["statue of deathstrike"] = 3000, + ["statue of devovorga"] = 1500, + ["statue of gnomevil"] = 2000, + ["stealth ring"] = 200, + ["steel boots"] = 30000, + ["steel helmet"] = 293, + ["steel shield"] = 80, + ["stone herb"] = 20, + ["stone nose"] = 590, + ["stone skin amulet"] = 500, + ["stone wing"] = 120, + ["stonerefiner's skull"] = 100, + ["strand of medusa hair"] = 600, + ["strange helmet"] = 500, + ["strange proto matter"] = 300, + ["strange symbol"] = 200, + ["strange talisman"] = 30, + ["striped fur"] = 50, + ["studded armor"] = 25, + ["studded club"] = 10, + ["studded helmet"] = 20, + ["studded legs"] = 15, + ["studded shield"] = 16, + ["stuffed dragon"] = 6000, + ["sulphurous stone"] = 100, + ["swamp grass"] = 20, + ["swamplair armor"] = 16000, + ["swampling club"] = 40, + ["swampling moss"] = 20, + ["swarmer antenna"] = 130, + ["sword"] = 25, + ["sword ring"] = 100, + ["tail of corruption"] = 240, + ["talon"] = 320, + ["tarantula egg"] = 80, + ["tarnished rhino figurine"] = 320, + ["tattered piece of robe"] = 120, + ["taurus mace"] = 500, + ["telescope eye"] = 1600, + ["tempest shield"] = 35000, + ["templar scytheblade"] = 200, + ["tentacle piece"] = 5000, + ["terra amulet"] = 1500, + ["terra boots"] = 2500, + ["terra hood"] = 2500, + ["terra legs"] = 11000, + ["terra mantle"] = 11000, + ["terra rod"] = 2000, + ["terramite eggs"] = 50, + ["terramite legs"] = 60, + ["terramite shell"] = 170, + ["terrorbird beak"] = 95, + ["thaian sword"] = 16000, + ["the avenger"] = 42000, + ["the handmaiden's protector"] = 50000, + ["the imperor's trident"] = 50000, + ["the ironworker"] = 50000, + ["the justice seeker"] = 40000, + ["the plasmother's remains"] = 50000, + ["thick fur"] = 150, + ["thorn"] = 100, + ["throwing knife"] = 2, + ["tiger eye"] = 350, + ["time ring"] = 100, + ["titan axe"] = 4000, + ["token of love"] = 440000, + ["tooth file"] = 60, + ["tooth of tazhadur"] = 10000, + ["torn shirt"] = 250, + ["tortoise shield"] = 150, + ["tower shield"] = 8000, + ["trapped bad dream monster"] = 900, + ["trashed draken boots"] = 40000, + ["tribal mask"] = 250, + ["troll green"] = 25, + ["trollroot"] = 50, + ["trophy of jaul"] = 4000, + ["trophy of obujos"] = 3000, + ["trophy of tanjis"] = 2000, + ["tunnel tyrant head"] = 500, + ["tunnel tyrant shell"] = 700, + ["turtle shell"] = 90, + ["tusk"] = 100, + ["tusk shield"] = 850, + ["twiceslicer"] = 28000, + ["twin hooks"] = 500, + ["two handed sword"] = 450, + ["undead heart"] = 200, + ["underworld rod"] = 4400, + ["unholy bone"] = 480, + ["unholy book"] = 30000, + ["unicorn figurine"] = 50000, + ["urmahlullu's mane"] = 490000, + ["urmahlullu's paw"] = 245000, + ["urmahlullu's tail"] = 210000, + ["utua's poison"] = 230, + ["vampire dust"] = 100, + ["vampire shield"] = 15000, + ["vampire teeth"] = 275, + ["vampire's cape chain"] = 150, + ["veal"] = 40, + ["vein of ore"] = 330, + ["velvet tapestry"] = 800, + ["venison"] = 55, + ["vexclaw talon"] = 1100, + ["vial"] = 5, + ["vial of hatred"] = 737000, + ["vibrant heart"] = 2100, + ["vibrant robe"] = 1200, + ["viking helmet"] = 66, + ["viking shield"] = 85, + ["vile axe"] = 30000, + ["violet crystal shard"] = 1500, + ["violet gem"] = 10000, + ["violet glass plate"] = 2150, + ["volatile proto matter"] = 300, + ["voodoo doll"] = 400, + ["wailing widow's necklace"] = 3000, + ["walnut"] = 80, + ["wand of cosmic energy"] = 2000, + ["wand of decay"] = 1000, + ["wand of defiance"] = 6500, + ["wand of draconia"] = 1500, + ["wand of dragonbreath"] = 200, + ["wand of everblazing"] = 6000, + ["wand of inferno"] = 3000, + ["wand of starstorm"] = 3600, + ["wand of voodoo"] = 4400, + ["wand of vortex"] = 100, + ["war axe"] = 12000, + ["war crystal"] = 460, + ["war hammer"] = 470, + ["war horn"] = 8000, + ["warmaster's wristguards"] = 200, + ["warrior helmet"] = 5000, + ["warrior's axe"] = 11000, + ["warrior's shield"] = 9000, + ["warwolf fur"] = 30, + ["waspoid claw"] = 320, + ["waspoid wing"] = 190, + ["watch"] = 6, + ["watermelon tourmaline"] = 30000, + ["weaver's wandtip"] = 250, + ["wedding ring"] = 100, + ["werebadger claws"] = 160, + ["werebadger skull"] = 185, + ["werebadger trophy"] = 9000, + ["werebear fur"] = 185, + ["werebear skull"] = 195, + ["werebear trophy"] = 11000, + ["wereboar hooves"] = 175, + ["wereboar loincloth"] = 1500, + ["wereboar trophy"] = 10000, + ["wereboar tusks"] = 165, + ["werefox tail"] = 200, + ["werefox trophy"] = 9000, + ["werehyaena nose"] = 220, + ["werehyaena talisman"] = 350, + ["werehyaena trophy"] = 12000, + ["werewolf amulet"] = 3000, + ["werewolf fangs"] = 180, + ["werewolf fur"] = 380, + ["white deer antlers"] = 400, + ["white deer skin"] = 245, + ["white gem"] = 12000, + ["white pearl"] = 160, + ["white piece of cloth"] = 100, + ["white silk flower"] = 9000, + ["widow's mandibles"] = 110, + ["wild flowers"] = 120, + ["wimp tooth chain"] = 120, + ["windborn colossus armor"] = 50000, + ["winged tail"] = 800, + ["winter wolf fur"] = 20, + ["witch broom"] = 60, + ["witch hat"] = 5000, + ["withered pauldrons"] = 850, + ["withered scalp"] = 900, + ["wolf paw"] = 70, + ["wolf tooth chain"] = 100, + ["wolf trophy"] = 3000, + ["wood"] = 5, + ["wood mushroom"] = 15, + ["wooden hammer"] = 15, + ["wooden shield"] = 5, + ["wool"] = 15, + ["writhing brain"] = 370000, + ["writhing heart"] = 185000, + ["wyrm scale"] = 400, + ["wyvern fang"] = 1500, + ["wyvern talisman"] = 265, + ["yellow gem"] = 1000, + ["yellow piece of cloth"] = 150, + ["yielocks"] = 600, + ["yielowax"] = 600, + ["yirkas' egg"] = 280, + ["young lich worm"] = 25000, + ["zaoan armor"] = 14000, + ["zaoan halberd"] = 500, + ["zaoan helmet"] = 45000, + ["zaoan legs"] = 14000, + ["zaoan robe"] = 12000, + ["zaoan shoes"] = 5000, + ["zaoan sword"] = 30000, + ["zaogun flag"] = 600, + ["zaogun shoulderplates"] = 150, + + -- 12.70 + ["carnisylvan bark"] = 230, + ["carnisylvan finger"] = 250, + ["human teeth"] = 2000, + ["abomination's eye"] = 650000, + ["abomination's tail"] = 700000, + ["abomination's tongue"] = 950000, + ["afflicted strider head"] = 900, + ["afflicted strider worms"] = 500, + ["bashmu fang"] = 600, + ["bashmu feather"] = 350, + ["bashmu tongue"] = 400, + ["blemished spawn abdomen"] = 550, + ["blemished spawn head"] = 800, + ["blemished spawn tail"] = 1000, + ["brainstealer's brain"] = 300000, + ["brainstealer's brainwave"] = 440000, + ["brainstealer's tissue"] = 240000, + ["cave chimera head"] = 1200, + ["cave chimera leg"] = 650, + ["curl of hair"] = 320000, + ["eyeless devourer legs"] = 650, + ["eyeless devourer maw"] = 420, + ["eyeless devourer tongue"] = 900, + ["girtablilu warrior carapace"] = 520, + ["lavafungus head"] = 900, + ["lavafungus ring"] = 390, + ["lavaworm jaws"] = 1100, + ["lavaworm spike roots"] = 600, + ["lavaworm spikes"] = 750, + ["old girtablilu carapace"] = 570, + ["old royal diary"] = 220000, + ["scorpion charm"] = 620, + ["tremendous tyrant head"] = 930, + ["tremendous tyrant shell"] = 740, + ["varnished diremaw brainpan"] = 750, + ["varnished diremaw legs"] = 670, + ["streaked devourer eyes"] = 500, + ["streaked devourer legs"] = 600, + ["streaked devourer maw"] = 400, + ["eldritch crystal"] = 48000, + + -- supplies + ["mana potion"] = 56, + ["strong mana potion"] = 93, + ["great mana potion"] = 144, + ["ultimate mana potion"] = 438, + ["health potion"] = 50, + ["strong health potion"] = 115, + ["great health potion"] = 225, + ["ultimate health potion"] = 379, + ["supreme health potion"] = 625, + ["great spirit potion"] = 228, + ["ultimate spirit potion"] = 438, + -- runes + ["cure poison rune"] = 65, + ["poison field rune"] = 21, + ["fire field rune"] = 28, + ["intense healing rune"] = 95, + ["destroy field rune"] = 15, + ["energy field rune"] = 38, + ["stalagmite rune"] = 12, + ["heavy magic missile rune"] = 12, + ["disintegrate rune"] = 26, + ["ultimate healing rune"] = 175, + ["poison bomb rune"] = 85, + ["animate death rune"] = 375, + ["chameleon rune"] = 210, + ["fireball rune"] = 30, + ["holy missile rune"] = 16, + ["icicle rune"] = 30, + ["stone shower rune"] = 37, + ["thunderstorm rune"] = 47, + ["avalanche rune"] = 57, + ["great fireball rune"] = 57, + ["convince creature rune"] = 80, + ["fire bomb rune"] = 147, + ["poison wall rune"] = 52, + ["explosion rune"] = 31, + ["fire wall rune"] = 61, + ["soulfire rune"] = 46, + ["wild growth rune"] = 160, + ["magic wall rune"] = 116, + ["energy wall rune"] = 85, + ["energy bomb rune"] = 203, + ["sudden death rune"] = 135, + ["paralyse rune"] = 700, + + ["envenomed arrow"] = 12, + ["flaming arrow"] = 5, + ["flash arrow"] = 5, + ["onyx arrow"] = 7, + ["poison arrow"] = 1, + ["shiver arrow"] = 5, + ["simple arrow"] = 2, + ["sniper arrow"] = 5, + ["tarsal arrow"] = 6, + ["arrow"] = 3, + ["burst arrow"] = 0, + ["crystalline arrow"] = 20, + ["diamond arrow"] = 100, + ["earth arrow"] = 5, + ["infernal bolt"] = 12, + ["piercing bolt"] = 5, + ["power bolt"] = 7, + ["prismatic bolt"] = 20, + ["spectral bolt"] = 70, + ["vortex bolt"] = 6, + ["bolt"] = 4, + ["drill bolt"] = 12, +} + +WasteItems = { + -- supplies + ["mana potion"] = 268, + ["strong mana potion"] = 237, + ["great mana potion"] = 238, + ["ultimate mana potion"] = 23373, + ["health potion"] = 266, + ["strong health potion"] = 236, + ["great health potion"] = 239, + ["ultimate health potion"] = 7643, + ["supreme health potion"] = 23375, + ["great spirit potion"] = 7642, + ["ultimate spirit potion"] = 23374, + -- ammo + ["envenomed arrow"] = 16143, + ["flaming arrow"] = 763, + ["flash arrow"] = 761, + ["onyx arrow"] = 7365, + ["poison arrow"] = 3448, + ["shiver arrow"] = 762, + ["simple arrow"] = 21470, + ["sniper arrow"] = 7364, + ["tarsal arrow"] = 14251, + ["arrow"] = 3447, + ["burst arrow"] = 3449, + ["crystalline arrow"] = 15793, + ["diamond arrow"] = 35901, + ["earth arrow"] = 774, + ["infernal bolt"] = 6528, + ["piercing bolt"] = 7363, + ["power bolt"] = 3450, + ["prismatic bolt"] = 16141, + ["spectral bolt"] = 35902, + ["vortex bolt"] = 14252, + ["bolt"] = 3446, + ["drill bolt"] = 16142, + -- runes + ["cure poison rune"] = 3153, + ["poison field rune"] = 3172, + ["fire field rune"] = 3188, + ["intense healing rune"] = 3152, + ["destroy field rune"] = 3148, + ["energy field rune"] = 3164, + ["stalagmite rune"] = 3179, + ["heavy magic missile rune"] = 3198, + ["disintegrate rune"] = 3197, + ["ultimate healing rune"] = 3160, + ["poison bomb rune"] = 3173, + ["animate death rune"] = 3203, + ["chameleon rune"] = 3178, + ["fireball rune"] = 3189, + ["holy missile rune"] = 3182, + ["icicle rune"] = 3158, + ["stone shower rune"] = 3175, + ["thunderstorm rune"] = 3202, + ["avalanche rune"] = 3161, + ["great fireball rune"] = 3191, + ["convince creature rune"] = 3177, + ["fire bomb rune"] = 3192, + ["poison wall rune"] = 3176, + ["explosion rune"] = 3200, + ["fire wall rune"] = 3190, + ["soulfire rune"] = 3195, + ["wild growth rune"] = 3156, + ["magic wall rune"] = 3180, + ["energy wall rune"] = 3166, + ["energy bomb rune"] = 3149, + ["sudden death rune"] = 3155, + ["paralyse rune"] = 3165 +} \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/main.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/main.lua new file mode 100644 index 0000000..4d21b6c --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/main.lua @@ -0,0 +1,40 @@ +local version = "4.5" +local currentVersion +local available = false + +storage.checkVersion = storage.checkVersion or 0 + +-- check max once per 12hours +if os.time() > storage.checkVersion + (12 * 60 * 60) then + + storage.checkVersion = os.time() + + HTTP.get("https://raw.githubusercontent.com/Vithrax/vBot/main/vBot/version.txt", function(data, err) + if err then + warn("[vBot updater]: Unable to check version:\n" .. err) + return + end + + currentVersion = data + available = true + end) + +end + +UI.Label("vBot v".. version .." \n Vithrax#5814") +UI.Button("Official OTCv8 Discord!", function() g_platform.openUrl("https://discord.gg/yhqBE4A") end) +UI.Separator() + +schedule(5000, function() + + if not available then return end + if currentVersion ~= version then + + UI.Separator() + UI.Label("New vBot is available for download! v"..currentVersion) + UI.Button("Go to vBot GitHub Page", function() g_platform.openUrl("https://github.com/Vithrax/vBot") end) + UI.Separator() + + end + +end) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/new_cavebot_lib.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/new_cavebot_lib.lua new file mode 100644 index 0000000..3434a99 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/new_cavebot_lib.lua @@ -0,0 +1,518 @@ +CaveBot = {} -- global namespace + +------------------------------------------------------------------- +-- CaveBot lib 1.0 +-- Contains a universal set of functions to be used in CaveBot + +----------------------[[ basic assumption ]]----------------------- +-- in general, functions cannot be slowed from within, only externally, by event calls, delays etc. +-- considering that and the fact that there is no while loop, every function return action +-- thus, functions will need to be verified outside themselfs or by another function +-- overall tips to creating extension: +-- - functions return action(nil) or true(done) +-- - extensions are controlled by retries var +------------------------------------------------------------------- + +-- local variables, constants and functions, used by global functions +local LOCKERS_LIST = {3497, 3498, 3499, 3500} +local LOCKER_ACCESSTILE_MODIFIERS = { + [3497] = {0,-1}, + [3498] = {1,0}, + [3499] = {0,1}, + [3500] = {-1,0} +} + +local function CaveBotConfigParse() + local name = storage["_configs"]["targetbot_configs"]["selected"] + if not name then + return warn("[vBot] Please create a new TargetBot config and reset bot") + end + local file = configDir .. "/targetbot_configs/" .. name .. ".json" + local data = g_resources.readFileContents(file) + return Config.parse(data)['looting'] +end + +local function getNearTiles(pos) + if type(pos) ~= "table" then + pos = pos:getPosition() + end + + local tiles = {} + local dirs = { + {-1, 1}, + {0, 1}, + {1, 1}, + {-1, 0}, + {1, 0}, + {-1, -1}, + {0, -1}, + {1, -1} + } + for i = 1, #dirs do + local tile = + g_map.getTile( + { + x = pos.x - dirs[i][1], + y = pos.y - dirs[i][2], + z = pos.z + } + ) + if tile then + table.insert(tiles, tile) + end + end + + return tiles +end + +-- ##################### -- +-- [[ Information class ]] -- +-- ##################### -- + +--- global variable to reflect current CaveBot status +CaveBot.Status = "waiting" + +--- Parses config and extracts loot list. +-- @return table +function CaveBot.GetLootItems() + local t = CaveBotConfigParse() and CaveBotConfigParse()["items"] or nil + + local returnTable = {} + if type(t) == "table" then + for i, item in pairs(t) do + table.insert(returnTable, item["id"]) + end + end + + return returnTable +end + + +--- Checks whether player has any visible items to be stashed +-- @return boolean +function CaveBot.HasLootItems() + for _, container in pairs(getContainers()) do + local name = container:getName():lower() + if not name:find("depot") and not name:find("your inbox") then + for _, item in pairs(container:getItems()) do + local id = item:getId() + if table.find(CaveBot.GetLootItems(), id) then + return true + end + end + end + end +end + +--- Parses config and extracts loot containers. +-- @return table +function CaveBot.GetLootContainers() + local t = CaveBotConfigParse() and CaveBotConfigParse()["containers"] or nil + + local returnTable = {} + if type(t) == "table" then + for i, container in pairs(t) do + table.insert(returnTable, container["id"]) + end + end + + return returnTable +end + +--- Information about open containers. +-- @param amount is boolean +-- @return table or integer +function CaveBot.GetOpenedLootContainers(containerTable) + local containers = CaveBot.GetLootContainers() + + local t = {} + for i, container in pairs(getContainers()) do + local containerId = container:getContainerItem():getId() + if table.find(containers, containerId) then + table.insert(t, container) + end + end + + return containerTable and t or #t +end + +--- Some actions needs to be additionally slowed down in case of high ping. +-- Maximum at 2000ms in case of lag spike. +-- @param multiplayer is integer +-- @return void +function CaveBot.PingDelay(multiplayer) + multiplayer = multiplayer or 1 + if ping() and ping() > 150 then -- in most cases ping above 150 affects CaveBot + local value = math.min(ping() * multiplayer, 2000) + return delay(value) + end +end + +-- ##################### -- +-- [[ Container class ]] -- +-- ##################### -- + +--- Closes any loot container that is open. +-- @return void or boolean +function CaveBot.CloseLootContainer() + local containers = CaveBot.GetLootContainers() + + for i, container in pairs(getContainers()) do + local containerId = container:getContainerItem():getId() + if table.find(containers, containerId) then + return g_game.close(container) + end + end + + return true +end + +function CaveBot.CloseAllLootContainers() + local containers = CaveBot.GetLootContainers() + + for i, container in pairs(getContainers()) do + local containerId = container:getContainerItem():getId() + if table.find(containers, containerId) then + g_game.close(container) + end + end + + return true +end + +--- Opens any loot container that isn't already opened. +-- @return void or boolean +function CaveBot.OpenLootContainer() + local containers = CaveBot.GetLootContainers() + + local t = {} + for i, container in pairs(getContainers()) do + local containerId = container:getContainerItem():getId() + table.insert(t, containerId) + end + + for _, container in pairs(getContainers()) do + for _, item in pairs(container:getItems()) do + local id = item:getId() + if table.find(containers, id) and not table.find(t, id) then + return g_game.open(item) + end + end + end + + return true +end + +-- ##################### -- +-- [[[ Position class ]] -- +-- ##################### -- + +--- Compares distance between player position and given pos. +-- @param position is table +-- @param distance is integer +-- @return boolean +function CaveBot.MatchPosition(position, distance) + local pPos = player:getPosition() + distance = distance or 1 + return getDistanceBetween(pPos, position) <= distance +end + +--- Stripped down to take less space. +-- Use only to safe position, like pz movement or reaching npc. +-- Needs to be called between 200-500ms to achieve fluid movement. +-- @param position is table +-- @param distance is integer +-- @return void +function CaveBot.GoTo(position, precision) + if not precision then + precision = 3 + end + return CaveBot.walkTo(position, 20, {ignoreCreatures = true, precision = precision}) +end + +--- Finds position of npc by name and reaches its position. +-- @return void(acion) or boolean +function CaveBot.ReachNPC(name) + name = name:lower() + + local npc = nil + for i, spec in pairs(getSpectators()) do + if spec:isNpc() and spec:getName():lower() == name then + npc = spec + end + end + + if not CaveBot.MatchPosition(npc:getPosition(), 3) then + CaveBot.GoTo(npc:getPosition()) + else + return true + end +end + +-- ##################### -- +-- [[[[ Depot class ]]]] -- +-- ##################### -- + +--- Reaches closest locker. +-- @return void(acion) or boolean + +local depositerLockerTarget = nil +local depositerLockerReachRetries = 0 +function CaveBot.ReachDepot() + local pPos = player:getPosition() + local tiles = getNearTiles(player:getPosition()) + + for i, tile in pairs(tiles) do + for i, item in pairs(tile:getItems()) do + if table.find(LOCKERS_LIST, item:getId()) then + depositerLockerTarget = nil + depositerLockerReachRetries = 0 + return true -- if near locker already then return function + end + end + end + + if depositerLockerReachRetries > 20 then + depositerLockerTarget = nil + depositerLockerReachRetries = 0 + end + + local candidates = {} + + if not depositerLockerTarget or distanceFromPlayer(depositerLockerTarget, pPos) > 12 then + for i, tile in pairs(g_map.getTiles(posz())) do + local tPos = tile:getPosition() + for i, item in pairs(tile:getItems()) do + if table.find(LOCKERS_LIST, item:getId()) then + local lockerTilePos = tile:getPosition() + lockerTilePos.x = lockerTilePos.x + LOCKER_ACCESSTILE_MODIFIERS[item:getId()][1] + lockerTilePos.y = lockerTilePos.y + LOCKER_ACCESSTILE_MODIFIERS[item:getId()][2] + local lockerTile = g_map.getTile(lockerTilePos) + if not lockerTile:hasCreature() then + if findPath(pos(), tPos, 20, {ignoreNonPathable = false, precision = 1, ignoreCreatures = true}) then + local distance = getDistanceBetween(tPos, pPos) + table.insert(candidates, {pos=tPos, dist=distance}) + end + end + end + end + end + + if #candidates > 1 then + table.sort(candidates, function(a,b) return a.dist < b.dist end) + end + end + + depositerLockerTarget = depositerLockerTarget or candidates[1].pos + + if depositerLockerTarget then + if not CaveBot.MatchPosition(depositerLockerTarget) then + depositerLockerReachRetries = depositerLockerReachRetries + 1 + return CaveBot.GoTo(depositerLockerTarget, 1) + else + depositerLockerReachRetries = 0 + depositerLockerTarget = nil + return true + end + end +end + +--- Opens locker item. +-- @return void(acion) or boolean +function CaveBot.OpenLocker() + local pPos = player:getPosition() + local tiles = getNearTiles(player:getPosition()) + + local locker = getContainerByName("Locker") + if not locker then + for i, tile in pairs(tiles) do + for i, item in pairs(tile:getItems()) do + if table.find(LOCKERS_LIST, item:getId()) then + local topThing = tile:getTopUseThing() + if not topThing:isNotMoveable() then + g_game.move(topThing, pPos, topThing:getCount()) + else + return g_game.open(item) + end + end + end + end + else + return true + end +end + +--- Opens depot chest. +-- @return void(acion) or boolean +function CaveBot.OpenDepotChest() + local depot = getContainerByName("Depot chest") + if not depot then + local locker = getContainerByName("Locker") + if not locker then + return CaveBot.OpenLocker() + end + for i, item in pairs(locker:getItems()) do + if item:getId() == 3502 then + return g_game.open(item, locker) + end + end + else + return true + end +end + +--- Opens inbox inside locker. +-- @return void(acion) or boolean +function CaveBot.OpenInbox() + local inbox = getContainerByName("Your inbox") + if not inbox then + local locker = getContainerByName("Locker") + if not locker then + return CaveBot.OpenLocker() + end + for i, item in pairs(locker:getItems()) do + if item:getId() == 12902 then + return g_game.open(item) + end + end + else + return true + end +end + +--- Opens depot box of given number. +-- @param index is integer +-- @return void or boolean +function CaveBot.OpenDepotBox(index) + local depot = getContainerByName("Depot chest") + if not depot then + return CaveBot.ReachAndOpenDepot() + end + + local foundParent = false + for i, container in pairs(getContainers()) do + if container:getName():lower():find("depot box") then + foundParent = container + break + end + end + if foundParent then return true end + + for i, container in pairs(depot:getItems()) do + if i == index then + return g_game.open(container) + end + end +end + +--- Reaches and opens depot. +-- Combined for shorthand usage. +-- @return boolean whether succeed to reach and open depot +function CaveBot.ReachAndOpenDepot() + if CaveBot.ReachDepot() and CaveBot.OpenDepotChest() then + return true + end + return false +end + +--- Reaches and opens imbox. +-- Combined for shorthand usage. +-- @return boolean whether succeed to reach and open depot +function CaveBot.ReachAndOpenInbox() + if CaveBot.ReachDepot() and CaveBot.OpenInbox() then + return true + end + return false +end + +--- Stripped down function to stash item. +-- @param item is object +-- @param index is integer +-- @param destination is object +-- @return void +function CaveBot.StashItem(item, index, destination) + destination = destination or getContainerByName("Depot chest") + if not destination then return false end + + return g_game.move(item, destination:getSlotPosition(index), item:getCount()) +end + +--- Withdraws item from depot chest or mail inbox. +-- main function for depositer/withdrawer +-- @param id is integer +-- @param amount is integer +-- @param fromDepot is boolean or integer +-- @param destination is object +-- @return void +function CaveBot.WithdrawItem(id, amount, fromDepot, destination) + if destination and type(destination) == "string" then + destination = getContainerByName(destination) + end + local itemCount = itemAmount(id) + local depot + for i, container in pairs(getContainers()) do + if container:getName():lower():find("depot box") or container:getName():lower():find("your inbox") then + depot = container + break + end + end + if not depot then + if fromDepot then + if not CaveBot.OpenDepotBox(fromDepot) then return end + else + return CaveBot.ReachAndOpenInbox() + end + return + end + if not destination then + for i, container in pairs(getContainers()) do + if container:getCapacity() > #container:getItems() and not string.find(container:getName():lower(), "quiver") and not string.find(container:getName():lower(), "depot") and not string.find(container:getName():lower(), "loot") and not string.find(container:getName():lower(), "inbox") then + destination = container + end + end + end + + if itemCount >= amount then + return true + end + + local toMove = amount - itemCount + for i, item in pairs(depot:getItems()) do + if item:getId() == id then + return g_game.move(item, destination:getSlotPosition(destination:getItemsCount()), math.min(toMove, item:getCount())) + end + end +end + +-- ##################### -- +-- [[[[[ Talk class ]]]] -- +-- ##################### -- + +--- Controlled by event caller. +-- Simple way to build npc conversations instead of multiline overcopied code. +-- @return void +function CaveBot.Conversation(...) + local expressions = {...} + local delay = storage.extras.talkDelay or 1000 + + local talkDelay = 0 + for i, expr in ipairs(expressions) do + schedule(talkDelay, function() NPC.say(expr) end) + talkDelay = talkDelay + delay + end +end + +--- Says hi trade to NPC. +-- Used as shorthand to open NPC trade window. +-- @return void +function CaveBot.OpenNpcTrade() + return CaveBot.Conversation("hi", "trade") +end + +--- Says hi destination yes to NPC. +-- Used as shorthand to travel. +-- @param destination is string +-- @return void +function CaveBot.Travel(destination) + return CaveBot.Conversation("hi", destination, "yes") +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/new_healer.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/new_healer.lua new file mode 100644 index 0000000..9e85304 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/new_healer.lua @@ -0,0 +1,455 @@ +setDefaultTab("Main") +local panelName = "newHealer" +local ui = setupUI([[ +Panel + height: 19 + + BotSwitch + id: title + anchors.top: parent.top + anchors.left: parent.left + text-align: center + width: 130 + !text: tr('Friend Healer') + + Button + id: edit + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 3 + height: 17 + text: Setup + +]]) +ui:setId(panelName) + +-- validate current settings +if not storage[panelName] or not storage[panelName].priorities then + storage[panelName] = nil +end + +if not storage[panelName] then + storage[panelName] = { + enabled = false, + customPlayers = {}, + vocations = {}, + groups = {}, + priorities = { + + {name="Custom Spell", enabled=false, custom=true}, + {name="Exura Gran Sio", enabled=true, strong = true}, + {name="Exura Sio", enabled=true, normal = true}, + {name="Exura Gran Mas Res", enabled=true, area = true}, + {name="Health Item", enabled=true, health=true}, + {name="Mana Item", enabled=true, mana=true} + + }, + settings = { + + {type="HealItem", text="Mana Item ", value=268}, + {type="HealScroll", text="Item Range: ", value=6}, + {type="HealItem", text="Health Item ", value=3160}, + {type="HealScroll", text="Mas Res Players: ", value=2}, + {type="HealScroll", text="Heal Friend at: ", value=80}, + {type="HealScroll", text="Use Gran Sio at: ", value=80}, + {type="HealScroll", text="Min Player HP%: ", value=80}, + {type="HealScroll", text="Min Player MP%: ", value=50}, + + }, + conditions = { + knights = true, + paladins = true, + druids = false, + sorcerers = false, + party = true, + guild = false, + botserver = false, + friends = false + } + } +end + +local config = storage[panelName] +local healerWindow = UI.createWindow('FriendHealer') +healerWindow:hide() +healerWindow:setId(panelName) + +ui.title:setOn(config.enabled) +ui.title.onClick = function(widget) + config.enabled = not config.enabled + widget:setOn(config.enabled) +end + +ui.edit.onClick = function() + healerWindow:show() + healerWindow:raise() + healerWindow:focus() +end + +local conditions = healerWindow.conditions +local targetSettings = healerWindow.targetSettings +local customList = healerWindow.customList +local priority = healerWindow.priority + +-- customList +-- create entries on the list +for name, health in pairs(config.customPlayers) do + local widget = UI.createWidget("HealerPlayerEntry", customList.playerList.list) + widget.remove.onClick = function() + config.customPlayers[name] = nil + widget:destroy() + end + widget:setText("["..health.."%] "..name) +end + +customList.playerList.onDoubleClick = function() + customList.playerList:hide() +end + +local function clearFields() + customList.addPanel.name:setText("friend name") + customList.addPanel.health:setText("1") + customList.playerList:show() +end + +local function capitalFistLetter(str) + return (string.gsub(str, "^%l", string.upper)) + end + +customList.addPanel.add.onClick = function() + local name = "" + local words = string.split(customList.addPanel.name:getText(), " ") + local health = tonumber(customList.addPanel.health:getText()) + for i, word in ipairs(words) do + name = name .. " " .. capitalFistLetter(word) + end + + if not health then + clearFields() + return warn("[Friend Healer] Please enter health percent value!") + end + + if name:len() == 0 or name:lower() == "friend name" then + clearFields() + return warn("[Friend Healer] Please enter friend name to be added!") + end + + if config.customPlayers[name] or config.customPlayers[name:lower()] then + clearFields() + return warn("[Friend Healer] Player already added to custom list.") + else + config.customPlayers[name] = health + local widget = UI.createWidget("HealerPlayerEntry", customList.playerList.list) + widget.remove.onClick = function() + config.customPlayers[name] = nil + widget:destroy() + end + widget:setText("["..health.."%] "..name) + end + + clearFields() +end + +local function validate(widget, category) + local list = widget:getParent() + local label = list:getParent().title + -- 1 - priorities | 2 - vocation + category = category or 0 + + if category == 2 and not storage.extras.checkPlayer then + label:setColor("#d9321f") + label:setTooltip("! WARNING ! \nTurn on check players in extras to use this feature!") + return + else + label:setColor("#dfdfdf") + label:setTooltip("") + end + + local checked = false + for i, child in ipairs(list:getChildren()) do + if category == 1 and child.enabled:isChecked() or child:isChecked() then + checked = true + end + end + + if not checked then + label:setColor("#d9321f") + label:setTooltip("! WARNING ! \nNo category selected!") + else + label:setColor("#dfdfdf") + label:setTooltip("") + end +end +-- targetSettings +targetSettings.vocations.box.knights:setChecked(config.conditions.knights) +targetSettings.vocations.box.knights.onClick = function(widget) + config.conditions.knights = not config.conditions.knights + widget:setChecked(config.conditions.knights) + validate(widget, 2) +end + +targetSettings.vocations.box.paladins:setChecked(config.conditions.paladins) +targetSettings.vocations.box.paladins.onClick = function(widget) + config.conditions.paladins = not config.conditions.paladins + widget:setChecked(config.conditions.paladins) + validate(widget, 2) +end + +targetSettings.vocations.box.druids:setChecked(config.conditions.druids) +targetSettings.vocations.box.druids.onClick = function(widget) + config.conditions.druids = not config.conditions.druids + widget:setChecked(config.conditions.druids) + validate(widget, 2) +end + +targetSettings.vocations.box.sorcerers:setChecked(config.conditions.sorcerers) +targetSettings.vocations.box.sorcerers.onClick = function(widget) + config.conditions.sorcerers = not config.conditions.sorcerers + widget:setChecked(config.conditions.sorcerers) + validate(widget, 2) +end + +targetSettings.groups.box.friends:setChecked(config.conditions.friends) +targetSettings.groups.box.friends.onClick = function(widget) + config.conditions.friends = not config.conditions.friends + widget:setChecked(config.conditions.friends) + validate(widget) +end + +targetSettings.groups.box.party:setChecked(config.conditions.party) +targetSettings.groups.box.party.onClick = function(widget) + config.conditions.party = not config.conditions.party + widget:setChecked(config.conditions.party) + validate(widget) +end + +targetSettings.groups.box.guild:setChecked(config.conditions.guild) +targetSettings.groups.box.guild.onClick = function(widget) + config.conditions.guild = not config.conditions.guild + widget:setChecked(config.conditions.guild) + validate(widget) +end + +targetSettings.groups.box.botserver:setChecked(config.conditions.botserver) +targetSettings.groups.box.botserver.onClick = function(widget) + config.conditions.botserver = not config.conditions.botserver + widget:setChecked(config.conditions.botserver) + validate(widget) +end + +validate(targetSettings.vocations.box.knights) +validate(targetSettings.groups.box.friends) +validate(targetSettings.vocations.box.sorcerers, 2) + +-- conditions +for i, setting in ipairs(config.settings) do + local widget = UI.createWidget(setting.type, conditions.box) + local text = setting.text + local val = setting.value + widget.text:setText(text) + + if setting.type == "HealScroll" then + widget.text:setText(widget.text:getText()..val) + if not (text:find("Range") or text:find("Mas Res")) then + widget.text:setText(widget.text:getText().."%") + end + widget.scroll:setValue(val) + widget.scroll.onValueChange = function(scroll, value) + setting.value = value + widget.text:setText(text..value) + if not (text:find("Range") or text:find("Mas Res")) then + widget.text:setText(widget.text:getText().."%") + end + end + if text:find("Range") or text:find("Mas Res") then + widget.scroll:setMaximum(10) + end + else + widget.item:setItemId(val) + widget.item:setShowCount(false) + widget.item.onItemChange = function(widget) + setting.value = widget:getItemId() + end + end +end + + + +-- priority and toggles +local function setCrementalButtons() + for i, child in ipairs(priority.list:getChildren()) do + if i == 1 then + child.increment:disable() + elseif i == 6 then + child.decrement:disable() + else + child.increment:enable() + child.decrement:enable() + end + end +end + +for i, action in ipairs(config.priorities) do + local widget = UI.createWidget("PriorityEntry", priority.list) + + widget:setText(action.name) + widget.increment.onClick = function() + local index = priority.list:getChildIndex(widget) + local table = config.priorities + + priority.list:moveChildToIndex(widget, index-1) + table[index], table[index-1] = table[index-1], table[index] + setCrementalButtons() + end + widget.decrement.onClick = function() + local index = priority.list:getChildIndex(widget) + local table = config.priorities + + priority.list:moveChildToIndex(widget, index+1) + table[index], table[index+1] = table[index+1], table[index] + setCrementalButtons() + end + widget.enabled:setChecked(action.enabled) + widget:setColor(action.enabled and "#98BF64" or "#dfdfdf") + widget.enabled.onClick = function() + action.enabled = not action.enabled + widget:setColor(action.enabled and "#98BF64" or "#dfdfdf") + widget.enabled:setChecked(action.enabled) + validate(widget, 1) + end + if action.custom then + widget.onDoubleClick = function() + local window = modules.client_textedit.show(widget, {title = "Custom Spell", description = "Enter below formula for a custom healing spell"}) + schedule(50, function() + window:raise() + window:focus() + end) + end + widget.onTextChange = function(widget,text) + action.name = text + end + widget:setTooltip("Double click to set spell formula.") + end + + if i == #config.priorities then + validate(widget, 1) + setCrementalButtons() + end +end + +local lastItemUse = now +local function friendHealerAction(spec, targetsInRange) + local name = spec:getName() + local health = spec:getHealthPercent() + local mana = spec:getManaPercent() + local dist = distanceFromPlayer(spec:getPosition()) + targetsInRange = targetsInRange or 0 + + local masResAmount = config.settings[4].value + local itemRange = config.settings[2].value + local healItem = config.settings[3].value + local manaItem = config.settings[1].value + local normalHeal = config.customPlayers[name] or config.settings[5].value + local strongHeal = config.customPlayers[name] and normalHeal/2 or config.settings[6].value + + for i, action in ipairs(config.priorities) do + if action.enabled then + if action.area and masResAmount <= targetsInRange and canCast("exura gran mas res") then + return say("exura gran mas res") + end + if action.mana and findItem(manaItem) and mana <= normalHeal and dist <= itemRange and now - lastItemUse > 1000 then + lastItemUse = now + return useWith(manaItem, spec) + end + if action.health and findItem(healItem) and health <= normalHeal and dist <= itemRange and now - lastItemUse > 1000 then + lastItemUse = now + return useWith(healItem, spec) + end + if action.strong and health <= strongHeal and not modules.game_cooldown.isCooldownIconActive(101) then + return say('exura gran sio "'..name) + end + if (action.normal or action.custom) and health <= normalHeal and canCast('exura sio "'..name) then + return say('exura sio "'..name) + end + end + end +end + +macro(100, function() + if not config.enabled then return end + if modules.game_cooldown.isGroupCooldownIconActive(2) then return end + + local minHp = config.settings[7].value + local minMp = config.settings[8].value + + -- first index will be heal target + local finalTable = {} + local inMasResRange = 0 + + -- check basic + if hppercent() <= minHp or manapercent() <= minMp then return end + + -- get all spectators + local spectators = getSpectators() + + -- clear table from irrelevant spectators + for i, spec in ipairs(getSpectators()) do + if spec:isLocalPlayer() or not spec:isPlayer() or not spec:canShoot() then + if not config.customPlayers[name] then + table.remove(spectators, table.find(spectators, spec)) + end + else + local specText = spec:getText() + -- check players is enabled and spectator already verified + if storage.extras.checkPlayer and specText:len() > 0 then + if specText:find("EK") and not config.conditions.knights or + specText:find("RP") and not config.conditions.paladins or + specText:find("ED") and not config.conditions.druids or + specText:find("MS") and not config.conditions.sorcerers then + if not config.customPlayers[name] then + table.remove(spectators, table.find(spectators, spec)) + end + end + end + local okParty = config.conditions.party and spec:isPartyMember() + local okFriend = config.conditions.friends and isFriend(spec) + local okGuild = config.conditions.guild and spec:getEmblem() == 1 + local okBotServer = config.conditions.botserver and vBot.BotServerMembers[spec:getName()] + if not (okParty or okFriend or okGuild or okBotServer) then + if not config.customPlayers[name] then + table.remove(spectators, table.find(spectators, spec)) + end + end + end + end + + -- no targets, return + if #spectators == 0 then return end + + for name, health in pairs(config.customPlayers) do + for i, spec in ipairs(spectators) do + local specHp = spec:getHealthPercent() + if spec:getName() == name and specHp <= health then + if distanceFromPlayer(spec:getPosition()) <= 2 then + inMasResRange = inMasResRange + 1 + end + table.insert(finalTable, spec) + table.remove(spectators, i) + end + end + end + + for i=1,#spectators do + local spec = spectators[i] + if distanceFromPlayer(spec:getPosition()) <= 3 then + inMasResRange = inMasResRange + 1 + end + table.insert(finalTable, spec) + end + + -- no targets, return + if #finalTable == 0 then return end + + friendHealerAction(finalTable[1], inMasResRange) +end) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/new_healer.otui b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/new_healer.otui new file mode 100644 index 0000000..2eb55cc --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/new_healer.otui @@ -0,0 +1,413 @@ +CategoryCheckBox < CheckBox + font: verdana-11px-rounded + margin-top: 3 + + $checked: + color: #98BF64 + +HealScroll < Panel + + ToolTipLabel + id: text + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-align: center + text: test + + HorizontalScrollBar + id: scroll + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 3 + minimum: 0 + maximum: 100 + step: 1 + +HealItem < Panel + + BotItem + id: item + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + size: 34 34 + + ToolTipLabel + id: text + anchors.fill: parent + anchors.left: prev.right + margin-left: 8 + text-wrap: true + text-align: left + +ToolTipLabel < UIWidget + font: verdana-11px-rounded + color: #dfdfdf + height: 14 + text-align: center + +HealerPlayerEntry < Label + background-color: alpha + text-offset: 5 1 + focusable: true + height: 16 + font: verdana-11px-rounded + text-align: left + + $focus: + background-color: #00000055 + + Button + id: remove + anchors.right: parent.right + margin-right: 2 + anchors.verticalCenter: parent.verticalCenter + size: 15 15 + margin-right: 15 + text: X + tooltip: Remove player from the list + +PriorityEntry < ToolTipLabel + background-color: alpha + text-offset: 18 1 + focusable: true + height: 16 + font: verdana-11px-rounded + text-align: left + + $focus: + background-color: #00000055 + + CheckBox + id: enabled + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + size: 15 15 + margin-top: 2 + margin-left: 3 + + Button + id: increment + anchors.right: parent.right + margin-right: 2 + anchors.verticalCenter: parent.verticalCenter + size: 14 14 + text: + + tooltip: Increase Priority + + Button + id: decrement + anchors.right: prev.left + margin-right: 2 + anchors.verticalCenter: parent.verticalCenter + size: 14 14 + text: - + tooltip: Decrease Priority + +TargetSettings < Panel + size: 280 125 + padding: 3 + image-source: /images/ui/window + image-border: 6 + + Label + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + font: verdana-11px-rounded + text: Heal Target Settings + + Groups + id: groups + anchors.top: prev.bottom + margin-top: 8 + anchors.left: parent.left + margin-left: 9 + + Vocations + id: vocations + anchors.left: prev.right + margin-left: 5 + anchors.verticalCenter: prev.verticalCenter + +Groups < FlatPanel + size: 150 90 + padding: 3 + padding-top: 5 + + ToolTipLabel + id: title + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + text: Groups + tooltip: Players added in custom list will always be in scope + + HorizontalSeparator + anchors.top: prev.bottom + margin-top: 2 + anchors.left: parent.left + anchors.right: parent.right + + Panel + id: box + anchors.top: prev.bottom + margin-top: 2 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + padding: 2 + layout: + type: verticalBox + + CategoryCheckBox + id: friends + text: Friends + + CategoryCheckBox + id: party + text: Party Members + + CategoryCheckBox + id: guild + text: Guild Members + + CategoryCheckBox + id: botserver + text: BotServer Members + +Vocations < FlatPanel + size: 100 90 + padding: 3 + padding-top: 5 + + ToolTipLabel + id: title + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + font: verdana-11px-rounded + text: Vocations + + HorizontalSeparator + anchors.top: prev.bottom + margin-top: 2 + anchors.left: parent.left + anchors.right: parent.right + + Panel + id: box + anchors.top: prev.bottom + margin-top: 2 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + padding: 2 + + layout: + type: verticalBox + + CategoryCheckBox + id: knights + text: Knights + + CategoryCheckBox + id: paladins + text: Paladins + + CategoryCheckBox + id: druids + text: Druids + + CategoryCheckBox + id: sorcerers + text: Sorcerers + +Priority < Panel + size: 190 123 + padding: 6 + padding-top: 3 + image-source: /images/ui/window + image-border: 6 + + ToolTipLabel + id: title + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + font: verdana-11px-rounded + text: Priority & Toggles + + TextList + id: list + anchors.top: prev.bottom + margin-top: 3 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + fit-children: true + padding-top: 1 + +AddPlayer < FlatPanel + padding: 5 + + Label + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + font: verdana-11px-rounded + text: Add Player to Custom List + text-align: center + text-wrap: true + + HorizontalSeparator + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 2 + + SpinBox + id: health + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 20 + width: 50 + minimum: 1 + maximum: 99 + step: 1 + focusable: true + text-align: center + + Label + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 3 + font: verdana-11px-rounded + text: %HP - heal if below + + TextEdit + id: name + anchors.top: health.bottom + margin-top: 5 + anchors.left: health.left + anchors.right: parent.right + font: verdana-11px-rounded + text-align: center + text: friend name + + Button + id: add + anchors.left: health.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 5 + font: verdana-11px-rounded + text: Add Player + +PlayerList < Panel + + TextList + id: list + anchors.fill: parent + fit-children: true + padding-top: 2 + vertical-scrollbar: listScrollBar + + VerticalScrollBar + id: listScrollBar + anchors.top: list.top + anchors.bottom: list.bottom + anchors.right: list.right + step: 14 + pixels-scroll: true + +CustomList < Panel + size: 190 172 + padding: 6 + padding-top: 3 + image-source: /images/ui/window + image-border: 6 + + ToolTipLabel + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + font: verdana-11px-rounded + text: Custom Player List + tooltip: Double click on the list below to add new player. + + AddPlayer + id: addPanel + anchors.top: prev.bottom + margin-top: 3 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + + PlayerList + id: playerList + anchors.fill: prev + +Conditions < Panel + size: 280 170 + padding: 3 + image-source: /images/ui/window + image-border: 6 + + Label + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + font: verdana-11px-rounded + text: Player Conditions + + Panel + id: box + anchors.fill: parent + margin-top: 16 + padding: 5 + padding-top: 3 + layout: + type: grid + cell-size: 128 31 + cell-spacing: 5 + num-columns: 2 + +FriendHealer < MainWindow + !text: tr('Friend Healer') + size: 512 390 + padding-top: 30 + @onEscape: self:hide() + + Conditions + id: conditions + anchors.top: parent.top + anchors.right: parent.right + + TargetSettings + id: targetSettings + anchors.top: prev.bottom + margin-top: 10 + anchors.left: prev.left + + Priority + id: priority + anchors.top: parent.top + anchors.left: parent.left + + CustomList + id: customList + anchors.top: priority.bottom + margin-top: 10 + anchors.left: priority.left + + HorizontalSeparator + id: separator + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: closeButton.top + margin-bottom: 8 + + Button + id: closeButton + !text: tr('Close') + font: cipsoftFont + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 45 21 + @onClick: self:getParent():hide() \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/npc_talk.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/npc_talk.lua new file mode 100644 index 0000000..4ed5cf4 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/npc_talk.lua @@ -0,0 +1,5 @@ +onAttackingCreatureChange(function(creature, OldCreature) + if creature and creature:isNpc() and distanceFromPlayer(creature:getPosition()) <= 3 then + CaveBot.Conversation("hi", "trade") + end +end) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/playerlist.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/playerlist.lua new file mode 100644 index 0000000..ea7d2b6 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/playerlist.lua @@ -0,0 +1,332 @@ +setDefaultTab("Main") +local tabs = {"Friends", "Enemies", "BlackList"} +local panelName = "playerList" +local colors = {"#03C04A", "#fc4c4e", "orange"} + +if not storage[panelName] then + storage[panelName] = { + enemyList = {}, + friendList = {}, + blackList = {}, + groupMembers = true, + outfits = false, + marks = false, + highlight = false + } +end + +local config = storage[panelName] +local playerTables = {config.friendList, config.enemyList, config.blackList} + +-- functions +local function clearCachedPlayers() + CachedFriends = {} + CachedEnemies = {} +end + +local refreshStatus = function() + for _, spec in ipairs(getSpectators()) do + if spec:isPlayer() and not spec:isLocalPlayer() then + if config.outfits then + local specOutfit = spec:getOutfit() + if isFriend(spec:getName()) then + if config.highlight then + spec:setMarked('#0000FF') + end + specOutfit.head = 88 + specOutfit.body = 88 + specOutfit.legs = 88 + specOutfit.feet = 88 + if storage.BOTserver.outfit then + local voc = vBot.BotServerMembers[spec:getName()] + specOutfit.addons = 3 + if voc == 1 then + specOutfit.type = 131 + elseif voc == 2 then + specOutfit.type = 129 + elseif voc == 3 then + specOutfit.type = 130 + elseif voc == 4 then + specOutfit.type = 144 + end + end + spec:setOutfit(specOutfit) + elseif isEnemy(spec:getName()) then + if config.highlight then + spec:setMarked('#FF0000') + end + specOutfit.head = 94 + specOutfit.body = 94 + specOutfit.legs = 94 + specOutfit.feet = 94 + spec:setOutfit(specOutfit) + end + end + end + end +end +refreshStatus() + +local checkStatus = function(creature) + if not creature:isPlayer() or creature:isLocalPlayer() then return end + + local specName = creature:getName() + local specOutfit = creature:getOutfit() + + if isFriend(specName) then + if config.highlight then + creature:setMarked('#0000FF') + end + if config.outfits then + specOutfit.head = 88 + specOutfit.body = 88 + specOutfit.legs = 88 + specOutfit.feet = 88 + if storage.BOTserver.outfit then + local voc = vBot.BotServerMembers[creature:getName()] + specOutfit.addons = 3 + if voc == 1 then + specOutfit.type = 131 + elseif voc == 2 then + specOutfit.type = 129 + elseif voc == 3 then + specOutfit.type = 130 + elseif voc == 4 then + specOutfit.type = 144 + end + end + creature:setOutfit(specOutfit) + end + elseif isEnemy(specName) then + if config.highlight then + creature:setMarked('#FF0000') + end + if config.outfits then + specOutfit.head = 94 + specOutfit.body = 94 + specOutfit.legs = 94 + specOutfit.feet = 94 + creature:setOutfit(specOutfit) + end + end +end + + +rootWidget = g_ui.getRootWidget() +if rootWidget then + local ListWindow = UI.createWindow('PlayerListWindow', rootWidget) + ListWindow:hide() + + UI.Button("Player Lists", function() + ListWindow:show() + ListWindow:raise() + ListWindow:focus() + end) + + -- settings + ListWindow.settings.Members:setChecked(config.groupMembers) + ListWindow.settings.Members.onClick = function(widget) + config.groupMembers = not config.groupMembers + if not config.groupMembers then + clearCachedPlayers() + end + refreshStatus() + widget:setChecked(config.groupMembers) + end + ListWindow.settings.Outfit:setChecked(config.outfits) + ListWindow.settings.Outfit.onClick = function(widget) + config.outfits = not config.outfits + widget:setChecked(config.outfits) + refreshStatus() + end + ListWindow.settings.NeutralsAreEnemy:setChecked(config.marks) + ListWindow.settings.NeutralsAreEnemy.onClick = function(widget) + config.marks = not config.marks + widget:setChecked(config.marks) + end + ListWindow.settings.Highlight:setChecked(config.highlight) + ListWindow.settings.Highlight.onClick = function(widget) + config.highlight = not config.highlight + widget:setChecked(config.highlight) + end + + ListWindow.settings.AutoAdd:setChecked(config.autoAdd) + ListWindow.settings.AutoAdd.onClick = function(widget) + config.autoAdd = not config.autoAdd + widget:setChecked(config.autoAdd) + end + + local TabBar = ListWindow.tmpTabBar + TabBar:setContentWidget(ListWindow.tmpTabContent) + local blacklistList + + for v = 1, 3 do + local listPanel = g_ui.createWidget("tPanel") -- Creates Panel + local playerList = playerTables[v] + listPanel:setId(tabs[v].."Tab") + TabBar:addTab(tabs[v], listPanel) + + -- elements + local addButton = listPanel.add + local nameTab = listPanel.name + local list = listPanel.list + if v == 3 then + blacklistList = list + end + + for i, name in ipairs(playerList) do + local label = UI.createWidget("PlayerLabel", list) + label:setText(name) + label.remove.onClick = function() + table.remove(playerList, table.find(playerList, name)) + label:destroy() + clearCachedPlayers() + refreshStatus() + end + label.onMouseRelease = function(widget, mousePos, mouseButton) + if mouseButton == 2 then + local child = rootWidget:recursiveGetChildByPos(mousePos) + if child == widget then + local menu = g_ui.createWidget('PopupMenu') + menu:setId("blzMenu") + menu:setGameMenu(true) + menu:addOption('Check Player', function() + local name = widget:getText():gsub(" ", "_") + local link = "https://www.gunzodus.net/character/show/" + g_platform.openUrl(link..name) + end, "") + menu:addOption('Copy Name', function() + g_window.setClipboardText(widget:getText()) + end, "") + menu:display(mousePos) + return true + end + end + end + end + + local tabButton = TabBar.buttonsPanel:getChildren()[v] + + tabButton.onStyleApply = function(widget) + if TabBar:getCurrentTab() == widget then + widget:setColor(colors[v]) + end + end + + -- callbacks + addButton.onClick = function() + local names = string.split(nameTab:getText(), ",") + + if #names == 0 then + warn("vBot[PlayerList]: Name is missing!") + return + end + + for i=1,#names do + local name = names[i]:trim() + if name:len() == 0 then + warn("vBot[PlayerList]: Name is missing!") + else + if not table.find(playerList, name) then + table.insert(playerList, name) + local label = UI.createWidget("PlayerLabel", list) + label:setText(name) + label.remove.onClick = function() + table.remove(playerList, table.find(playerList, name)) + label:destroy() + end + label.onMouseRelease = function(widget, mousePos, mouseButton) + if mouseButton == 2 then + local child = rootWidget:recursiveGetChildByPos(mousePos) + if child == widget then + local menu = g_ui.createWidget('PopupMenu') + menu:setId("blzMenu") + menu:setGameMenu(true) + menu:addOption('Check Player', function() + local name = widget:getText():gsub(" ", "_") + local link = "https://www.gunzodus.net/character/show/" + g_platform.openUrl(link..name) + end, "") + menu:addOption('Copy Name', function() + g_window.setClipboardText(widget:getText()) + end, "") + menu:display(mousePos) + return true + end + end + end + nameTab:setText("") + else + warn("vBot[PlayerList]: Player ".. name .." is already added!") + nameTab:setText("") + end + clearCachedPlayers() + refreshStatus() + end + end + end + + nameTab.onKeyPress = function(widget, keyCode, keyboardModifiers) + if keyCode ~= 5 then + return false + end + addButton.onClick() + return true + end + end + + function addBlackListPlayer(name) + if table.find(config.blackList, name) then return end + + table.insert(config.blackList, name) + local label = UI.createWidget("PlayerLabel", blacklistList) + label:setText(name) + label.remove.onClick = function() + table.remove(playerList, table.find(playerList, name)) + label:destroy() + end + label.onMouseRelease = function(widget, mousePos, mouseButton) + if mouseButton == 2 then + local child = rootWidget:recursiveGetChildByPos(mousePos) + if child == widget then + local menu = g_ui.createWidget('PopupMenu') + menu:setId("blzMenu") + menu:setGameMenu(true) + menu:addOption('Check Player', function() + local name = widget:getText():gsub(" ", "_") + local link = "https://www.gunzodus.net/character/show/" + g_platform.openUrl(link..name) + end, "") + menu:addOption('Copy Name', function() + g_window.setClipboardText(widget:getText()) + end, "") + menu:display(mousePos) + return true + end + end + end + end +end + +onTextMessage(function(mode,text) + if not config.autoAdd then return end + if CaveBot.isOff() or TargetBot.isOff() then return end + if not text:find("Warning! The murder of") then return end + + text = string.split(text, "Warning! The murder of ")[1] + text = string.split(text, " was not justified.")[1] + + addBlackListPlayer(text) +end) + +onCreatureAppear(function(creature) + checkStatus(creature) + end) + +onPlayerPositionChange(function(x,y) + if x.z ~= y.z then + schedule(20, function() + refreshStatus() + end) + end +end) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/playerlist.otui b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/playerlist.otui new file mode 100644 index 0000000..a4f0d92 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/playerlist.otui @@ -0,0 +1,151 @@ +PlayerLabel < UIWidget + background-color: alpha + text-offset: 3 1 + focusable: true + height: 16 + font: verdana-11px-rounded + text-align: left + + $focus: + background-color: #00000055 + + Button + id: remove + !text: tr('X') + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + width: 14 + height: 14 + margin-right: 15 + text-align: center + text-offset: 0 1 + tooltip: Remove profile from the list. + +SettingCheckBox < CheckBox + text-wrap: true + text-auto-resize: true + margin-top: 3 + font: verdana-11px-rounded + +Settings < FlatPanel + padding: 6 + layout: + type: verticalBox + + Label + text: Additional Settings + text-align: center + font: verdana-11px-rounded + + HorizontalSeparator + + SettingCheckBox + id: Members + margin-top: 6 + text: Consider group members as friends. + + SettingCheckBox + id: Outfit + text: Color listed player outfits to red or blue. + + SettingCheckBox + id: NeutralsAreEnemy + text: Consider every non friend player as enemy. + + SettingCheckBox + id: Highlight + text: Hightlight listed players in red or blue color. + + SettingCheckBox + id: AutoAdd + text: Automatically add killed players while cave botting to blacklist. + +tPanel < Panel + margin: 3 + padding: 3 + + TextList + id: list + height: 200 + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + vertical-scrollbar: listScrollBar + + VerticalScrollBar + id: listScrollBar + anchors.top: list.top + anchors.bottom: list.bottom + anchors.right: list.right + step: 14 + pixels-scroll: true + + TextEdit + id: name + anchors.top: list.bottom + margin-top: 3 + anchors.left: parent.left + anchors.right: parent.right + + Button + id: add + text: Add Player + anchors.top: prev.bottom + margin-top: 3 + anchors.left: parent.left + anchors.right: parent.right + font: verdana-11px-rounded + +PlayerListWindow < MainWindow + !text: tr('Player List') + size: 405 356 + @onEscape: self:hide() + + TabBar + id: tmpTabBar + anchors.top: parent.top + anchors.left: parent.left + width: 180 + + FlatPanel + id: tmpTabContent + anchors.top: tmpTabBar.bottom + anchors.left: parent.left + width: 180 + anchors.bottom: separator.top + margin-bottom: 5 + + VerticalSeparator + id: verticalSep + anchors.top: parent.top + anchors.bottom: separator.top + margin-bottom: 5 + anchors.horizontalCenter: parent.horizontalCenter + + Settings + id: settings + anchors.left: prev.right + anchors.top: parent.top + anchors.right: parent.right + anchors.bottom: next.top + margin: 3 + margin-left: 6 + margin-bottom: 4 + + HorizontalSeparator + id: separator + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: closeButton.top + margin-bottom: 8 + + Button + id: closeButton + !text: tr('Close') + font: cipsoftFont + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 45 21 + margin-top: 15 + margin-right: 5 + @onClick: self:getParent():hide() \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/pushmax.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/pushmax.lua new file mode 100644 index 0000000..0f63447 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/pushmax.lua @@ -0,0 +1,287 @@ +---@diagnostic disable: undefined-global +setDefaultTab("Main") + +local panelName = "pushmax" +local ui = setupUI([[ +Panel + height: 19 + + BotSwitch + id: title + anchors.top: parent.top + anchors.left: parent.left + text-align: center + width: 130 + !text: tr('PUSHMAX') + + Button + id: push + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 3 + height: 17 + text: Setup + +]]) +ui:setId(panelName) + +if not storage[panelName] then + storage[panelName] = { + enabled = true, + pushDelay = 1060, + pushMaxRuneId = 3188, + mwallBlockId = 2128, + pushMaxKey = "PageUp" + } +end + +local config = storage[panelName] + +ui.title:setOn(config.enabled) +ui.title.onClick = function(widget) +config.enabled = not config.enabled +widget:setOn(config.enabled) +end + +ui.push.onClick = function(widget) + pushWindow:show() + pushWindow:raise() + pushWindow:focus() +end + +rootWidget = g_ui.getRootWidget() +if rootWidget then + pushWindow = UI.createWindow('PushMaxWindow', rootWidget) + pushWindow:hide() + + pushWindow.closeButton.onClick = function(widget) + pushWindow:hide() + end + + local updateDelayText = function() + pushWindow.delayText:setText("Push Delay: ".. config.pushDelay) + end + updateDelayText() + pushWindow.delay.onValueChange = function(scroll, value) + config.pushDelay = value + updateDelayText() + end + pushWindow.delay:setValue(config.pushDelay) + + pushWindow.runeId.onItemChange = function(widget) + config.pushMaxRuneId = widget:getItemId() + end + pushWindow.runeId:setItemId(config.pushMaxRuneId) + pushWindow.mwallId.onItemChange = function(widget) + config.mwallBlockId = widget:getItemId() + end + pushWindow.mwallId:setItemId(config.mwallBlockId) + + pushWindow.hotkey.onTextChange = function(widget, text) + config.pushMaxKey = text + end + pushWindow.hotkey:setText(config.pushMaxKey) +end + + +-- variables for config +local fieldTable = {2118, 105, 2122} +local cleanTile = nil + +-- scripts + +local targetTile +local pushTarget + +local resetData = function() + for i, tile in pairs(g_map.getTiles(posz())) do + if tile:getText() == "TARGET" or tile:getText() == "DEST" or tile:getText() == "CLEAR" then + tile:setText('') + end + end + pushTarget = nil + targetTile = nil + cleanTile = nil +end + +local getCreatureById = function(id) + for i, spec in ipairs(getSpectators()) do + if spec:getId() == id then + return spec + end + end + return false +end + +local isNotOk = function(t,tile) + local tileItems = {} + + for i, item in pairs(tile:getItems()) do + table.insert(tileItems, item:getId()) + end + for i, field in ipairs(t) do + if table.find(tileItems, field) then + return true + end + end + return false +end + +local isOk = function(a,b) + return getDistanceBetween(a,b) == 1 +end + +-- to mark +local hold = 0 +onKeyDown(function(keys) + if not config.enabled then return end + if keys ~= config.pushMaxKey then return end + hold = now + local tile = getTileUnderCursor() + if not tile then return end + if pushTarget and targetTile then + resetData() + return + end + local creature = tile:getCreatures()[1] + if not pushTarget and creature then + pushTarget = creature + if pushTarget then + tile:setText('TARGET') + pushTarget:setMarked('#00FF00') + end + elseif not targetTile and pushTarget then + if pushTarget and getDistanceBetween(tile:getPosition(),pushTarget:getPosition()) ~= 1 then + resetData() + return + else + tile:setText('DEST') + targetTile = tile + end + end +end) + +-- mark tile to throw anything from it +onKeyPress(function(keys) + if not config.enabled then return end + if keys ~= config.pushMaxKey then return end + local tile = getTileUnderCursor() + if not tile then return end + + if (hold - now) < -2500 then + if cleanTile and tile ~= cleanTile then + resetData() + elseif not cleanTile then + cleanTile = tile + tile:setText("CLEAR") + end + end + hold = 0 +end) + +onCreaturePositionChange(function(creature, newPos, oldPos) + if not config.enabled then return end + if creature == player then + resetData() + end + if not pushTarget or not targetTile then return end + if creature == pushTarget and newPos == targetTile then + resetData() + end +end) + +macro(50, function() + if not config.enabled then return end + + local pushDelay = tonumber(config.pushDelay) + local rune = tonumber(config.pushMaxRuneId) + local customMwall = config.mwallBlockId + + if cleanTile then + local tilePos = cleanTile:getPosition() + local pPos = player:getPosition() + if not isOk(tilePos, pPos) then + resetData() + return + end + + if not cleanTile:hasCreature() then return end + local tiles = getNearTiles(tilePos) + local destTile + local forbidden = {} + -- unfortunately double loop + for i, tile in pairs(tiles) do + local minimapColor = g_map.getMinimapColor(tile:getPosition()) + local stairs = (minimapColor >= 210 and minimapColor <= 213) + if stairs then + table.insert(forbidden, tile:getPosition()) + end + end + for i, tile in pairs(tiles) do + local minimapColor = g_map.getMinimapColor(tile:getPosition()) + local stairs = (minimapColor >= 210 and minimapColor <= 213) + if tile:isWalkable() and not isNotOk(fieldTable, tile) and not tile:hasCreature() and not stairs then + local tooClose = false + if #forbidden ~= 0 then + for i=1,#forbidden do + local pos = forbidden[i] + if isOk(pos, tile:getPosition()) then + tooClose = true + break + end + end + end + if not tooClose then + destTile = tile + break + end + end + end + + if not destTile then return end + local parcel = cleanTile:getCreatures()[1] + if parcel then + test() + g_game.move(parcel,destTile:getPosition()) + delay(2000) + end + else + if not pushTarget or not targetTile then return end + local tilePos = targetTile:getPosition() + local targetPos = pushTarget:getPosition() + if not isOk(tilePos,targetPos) then return end + + local tileOfTarget = g_map.getTile(targetPos) + + if not targetTile:isWalkable() then + local topThing = targetTile:getTopUseThing():getId() + if topThing == 2129 or topThing == 2130 or topThing == customMwall then + if targetTile:getTimer() < pushDelay+500 then + vBot.isUsing = true + schedule(pushDelay+700, function() + vBot.isUsing = false + end) + end + if targetTile:getTimer() > pushDelay then + return + end + else + return resetData() + end + end + + if not tileOfTarget:getTopUseThing():isNotMoveable() and targetTile:getTimer() < pushDelay+500 then + return useWith(rune, pushTarget) + end + if isNotOk(fieldTable, targetTile) then + if targetTile:canShoot() then + return useWith(3148, targetTile:getTopUseThing()) + else + return + end + end + g_game.move(pushTarget,tilePos) + delay(2000) + end +end) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/pushmax.otui b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/pushmax.otui new file mode 100644 index 0000000..875a4f8 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/pushmax.otui @@ -0,0 +1,85 @@ +PushMaxWindow < MainWindow + !text: tr('Pushmax Settings') + size: 200 240 + @onEscape: self:hide() + + BotLabel + id: delayText + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-align: center + + HorizontalScrollBar + id: delay + anchors.left: delayText.left + anchors.right: delayText.right + anchors.top: delayText.bottom + margin-top: 5 + minimum: 800 + maximum: 2000 + step: 10 + + Label + id: label2 + anchors.top: delay.bottom + anchors.left: parent.horizontalCenter + anchors.right: parent.right + text-align: center + text: Custom WallID + margin-top: 5 + + Label + id: label3 + anchors.top: delay.bottom + anchors.right: parent.horizontalCenter + anchors.left: parent.left + text-align: center + text: VS AntiPush + margin-top: 5 + + BotItem + id: runeId + anchors.horizontalCenter: label3.horizontalCenter + anchors.top: label3.bottom + margin-top: 5 + + BotItem + id: mwallId + anchors.horizontalCenter: label2.horizontalCenter + anchors.top: label2.bottom + margin-top: 5 + + Label + id: label1 + anchors.top: mwallId.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 10 + text-align: center + text: Hotkey for PUSHMAX + + TextEdit + id: hotkey + anchors.left: parent.left + anchors.right: parent.right + anchors.top: label1.bottom + margin-top: 5 + text-align: center + + HorizontalSeparator + id: separator + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: closeButton.top + margin-bottom: 8 + + Button + id: closeButton + !text: tr('Close') + font: cipsoftFont + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 45 21 + margin-top: 15 + margin-right: 5 \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/quiver_label.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/quiver_label.lua new file mode 100644 index 0000000..671d726 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/quiver_label.lua @@ -0,0 +1,58 @@ +local quiverSlot = modules.game_inventory.inventoryWindow:recursiveGetChildById('slot5') +local label = quiverSlot.count + +label = label or g_ui.loadUIFromString([[ +Label + id: count + color: #bfbfbf + font: verdana-11px-rounded + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + text-align: right + margin-right: 3 + margin-left: 3 + text: +]], quiverSlot) + + +function getQuiverAmount() + -- old tibia + if g_game.getClientVersion() < 1000 then return end + + + local isQuiverEquipped = getRight() and getRight():isContainer() or false + local quiver = isQuiverEquipped and getContainerByItem(getRight():getId()) + local count = 0 + + if quiver then + for i, item in ipairs(quiver:getItems()) do + count = count + item:getCount() + end + else + return label:setText("") + end + + return label:setText(count) +end +getQuiverAmount() + +onContainerOpen(function(container, previousContainer) + getQuiverAmount() +end) + +onContainerClose(function(container) + getQuiverAmount() +end) + +onAddItem(function(container, slot, item, oldItem) + getQuiverAmount() +end) + +onRemoveItem(function(container, slot, item) + getQuiverAmount() +end) + +onContainerUpdateItem(function(container, slot, item, oldItem) + getQuiverAmount() +end) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/quiver_manager.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/quiver_manager.lua new file mode 100644 index 0000000..8c629cd --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/quiver_manager.lua @@ -0,0 +1,90 @@ +if voc() == 2 or voc() == 12 then + local bows = { 3350, 31581, 27455, 8027, 20082, 36664, 7438, 28718, 36665, 14246, 19362, 35518, 34150, 29417, 9378, 16164, 22866, 12733, 8029, 20083, 20084, 8026, 8028, 34088} + local xbows = { 30393, 3349, 27456, 20085, 16163, 5947, 8021, 14247, 22867, 8023, 22711, 19356, 20086, 20087, 34089} + local arrows = { 16143, 763, 761, 7365, 3448, 762, 21470, 7364, 14251, 3447, 3449, 15793, 25757, 774, 35901 } + local bolts = { 6528, 7363, 3450, 16141, 25758, 14252, 3446, 16142, 35902 } + local hold = false + + onContainerOpen(function(container, previousContainer) + hold = false + end) + + onContainerClose(function(container) + hold = false + end) + + onAddItem(function(container, slot, item, oldItem) + hold = false + end) + + onRemoveItem(function(container, slot, item) + hold = false + end) + + onContainerUpdateItem(function(container, slot, item, oldItem) + hold = false + end) + + + + local function manageQuiver(isBowEquipped, quiverContainer) + local ammo = isBowEquipped and arrows or bolts + local dest = nil + local containers = getContainers() + for i, container in ipairs(containers) do + if container ~= quiverContainer and not containerIsFull(container) then + if container:getName():lower():find("backpack") or container:getName():lower():find("bag") or container:getName():lower():find("chess") then + dest = container + end + end + end + + -- clearing + if dest then + for i, item in ipairs(quiverContainer:getItems()) do + if not table.find(ammo, item:getId()) then + local pos = dest:getSlotPosition(dest:getItemsCount()) + return g_game.move(item, pos, item:getCount()) + end + end + end + + if not containerIsFull(quiverContainer) then + for i, container in ipairs(containers) do + if container ~= quiverContainer then + for j, item in ipairs(container:getItems()) do + if table.find(ammo, item:getId()) then + local pos = quiverContainer:getSlotPosition(quiverContainer:getItemsCount()) + return g_game.move(item, pos, item:getCount()) + end + end + end + end + end + return true + end + + UI.Separator() + macro(100, "Quiver Manager", function() + if hold then return end -- do nothing if nothing to do + local hand = getLeft() and getLeft():getId() + local quiverEquipped = getRight() and getRight():isContainer() + + if not hand then return end + if not quiverEquipped then return end + + local quiverContainer = getContainerByItem(getRight():getId()) + if not quiverContainer then return end + + local isBowEquipped = getLeft() and table.find(bows, hand) and true or false + if not isBowEquipped then + if not table.find(xbows, hand) then + return -- neither bow and xbow is equipped + end + end + + if manageQuiver(isBowEquipped, quiverContainer) then -- if true then it didn't do anything + hold = true + end + end) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/siolist.otui b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/siolist.otui new file mode 100644 index 0000000..e992075 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/siolist.otui @@ -0,0 +1,192 @@ +VocationPanel < Panel + padding: 3 + image-source: /images/ui/panel_flat + image-border: 6 + size: 190 55 + + Label + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-align: center + text: for BotServer, Heal only: + + BotSwitch + id: ED + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.horizontalCenter + text: Druids + + BotSwitch + id: MS + anchors.bottom: parent.bottom + anchors.left: parent.horizontalCenter + anchors.right: parent.right + text: Sorcerers + + BotSwitch + id: EK + anchors.bottom: ED.top + anchors.left: parent.left + anchors.right: parent.horizontalCenter + text: Knights + + BotSwitch + id: RP + anchors.bottom: ED.top + anchors.left: parent.horizontalCenter + anchors.right: parent.right + text: Paladins + + + +SioListWindow < MainWindow + !text: tr('Healer Options') + size: 220 360 + @onEscape: self:hide() + + BotSwitch + id: exuraSio + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.horizontalCenter + text: Exura Sio + margin-right: 2 + + BotSwitch + id: exuraGranSio + anchors.top: parent.top + anchors.left: prev.right + anchors.right: parent.right + text: Exura Gran Sio + margin-left: 2 + + BotSwitch + id: exuraMasRes + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + text: Exura Gran Mas Res + margin-top: 3 + + BotSwitch + id: spell + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + text: Custom Spell + margin-top: 3 + text-align: center + + BotTextEdit + id: spellName + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 3 + + HorizontalSeparator + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 10 + + BotItem + id: itemId + anchors.top: prev.bottom + anchors.left: parent.left + margin-top: 10 + + BotSwitch + id: item + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + anchors.bottom: prev.verticalCenter + text-align: center + text: Item Healing + margin-left: 2 + + BotLabel + id: distText + anchors.top: itemId.verticalCenter + anchors.left: itemId.right + anchors.right: parent.right + anchors.bottom: itemId.bottom + text-align: center + text: Max Distance + + HorizontalScrollBar + id: Distance + anchors.left: parent.left + anchors.top: itemId.bottom + anchors.right: parent.right + margin-top: 3 + minimum: 1 + maximum: 10 + step: 1 + + HorizontalSeparator + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 8 + + BotLabel + id: manaInfo + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + text-align: center + margin-top: 5 + + HorizontalScrollBar + id: minMana + anchors.left: spellName.left + anchors.right: spellName.right + anchors.top: manaInfo.bottom + margin-top: 2 + minimum: 1 + maximum: 100 + step: 1 + + BotLabel + id: friendHp + anchors.left: spellName.left + anchors.right: spellName.right + anchors.top: prev.bottom + text-align: center + margin-top: 5 + + HorizontalScrollBar + id: minFriendHp + anchors.left: spellName.left + anchors.right: spellName.right + anchors.top: friendHp.bottom + margin-top: 2 + minimum: 1 + maximum: 100 + step: 1 + + VocationPanel + id: vocation + anchors.top: prev.bottom + margin-top: 6 + + HorizontalSeparator + id: separator + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: closeButton.top + margin-bottom: 8 + + Button + id: closeButton + !text: tr('Close') + font: cipsoftFont + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 45 21 + margin-top: 15 + margin-right: 5 \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/spy_level.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/spy_level.lua new file mode 100644 index 0000000..f225d76 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/spy_level.lua @@ -0,0 +1,24 @@ +-- config + +local keyUp = "=" +local keyDown = "-" +setDefaultTab("Tools") + +-- script + +local lockedLevel = pos().z + +onPlayerPositionChange(function(newPos, oldPos) + lockedLevel = pos().z + modules.game_interface.getMapPanel():unlockVisibleFloor() +end) + +onKeyPress(function(keys) + if keys == keyDown then + lockedLevel = lockedLevel + 1 + modules.game_interface.getMapPanel():lockVisibleFloor(lockedLevel) + elseif keys == keyUp then + lockedLevel = lockedLevel - 1 + modules.game_interface.getMapPanel():lockVisibleFloor(lockedLevel) + end +end) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/supplies.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/supplies.lua new file mode 100644 index 0000000..4e99673 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/supplies.lua @@ -0,0 +1,383 @@ +function SuppliesPanel(parent) + local panelName = "supplies" + if not parent then + parent = panel + end + + local temp = nil + if SuppliesConfig[panelName] and SuppliesConfig[panelName].item1 then + temp = SuppliesConfig[panelName] + end + + +if not SuppliesConfig[panelName] or SuppliesConfig[panelName].item1 then + SuppliesConfig[panelName] = { + currentProfile = "Default", + ["Default"] = {} + } + if temp then + SuppliesConfig[panelName].Default = temp + end +end + +local currentProfile = SuppliesConfig[panelName].currentProfile +local config = SuppliesConfig[panelName][currentProfile] + +if not config then + for k,v in pairs(SuppliesConfig[panelName]) do + if type(v) == "table" then + SuppliesConfig[panelName].currentProfile = k + config = SuppliesConfig[panelName][k] + break + end + end +end + +rootWidget = g_ui.getRootWidget() +if rootWidget then + SuppliesWindow = UI.createWindow('SuppliesWindow', rootWidget) + SuppliesWindow:hide() + + SuppliesWindow.onVisibilityChange = function(widget, visible) + if not visible then + vBotConfigSave("supply") + end + end + + local function loadVariables() + config.item1 = config.item1 or 0 + config.item2 = config.item2 or 0 + config.item3 = config.item3 or 0 + config.item4 = config.item4 or 0 + config.item5 = config.item5 or 0 + config.item6 = config.item6 or 0 + config.item1Min = config.item1Min or 0 + config.item1Max = config.item1Max or 0 + config.item2Min = config.item2Min or 0 + config.item2Max = config.item2Max or 0 + config.item3Min = config.item3Min or 0 + config.item3Max = config.item3Max or 0 + config.item4Min = config.item4Min or 0 + config.item4Max = config.item4Max or 0 + config.item5Min = config.item5Min or 0 + config.item5Max = config.item5Max or 0 + config.item6Min = config.item6Min or 0 + config.item6Max = config.item6Max or 0 + config.capValue = config.capValue or 0 + config.staminaValue = config.staminaValue or 0 + end + loadVariables() + + local function setValues() + SuppliesWindow.capSwitch:setOn(config.capSwitch) + SuppliesWindow.SoftBoots:setOn(config.SoftBoots) + SuppliesWindow.imbues:setOn(config.imbues) + SuppliesWindow.staminaSwitch:setOn(config.staminaSwitch) + SuppliesWindow.item1:setItemId(config.item1) + SuppliesWindow.item2:setItemId(config.item2) + SuppliesWindow.item3:setItemId(config.item3) + SuppliesWindow.item4:setItemId(config.item4) + SuppliesWindow.item5:setItemId(config.item5) + SuppliesWindow.capValue:setText(config.capValue) + SuppliesWindow.item1Min:setText(config.item1Min) + SuppliesWindow.item1Max:setText(config.item1Max) + SuppliesWindow.item2Min:setText(config.item2Min) + SuppliesWindow.item2Max:setText(config.item2Max) + SuppliesWindow.item3Min:setText(config.item3Min) + SuppliesWindow.item3Max:setText(config.item3Max) + SuppliesWindow.item4Min:setText(config.item4Min) + SuppliesWindow.staminaValue:setText(config.staminaValue) + SuppliesWindow.item4Max:setText(config.item4Max) + SuppliesWindow.item5Min:setText(config.item5Min) + SuppliesWindow.item5Max:setText(config.item5Max) + SuppliesWindow.item6Min:setText(config.item6Min) + SuppliesWindow.item6Max:setText(config.item6Max) + end + setValues() + + local function refreshProfileList() + local profiles = SuppliesConfig[panelName] + + SuppliesWindow.profiles:destroyChildren() + for k,v in pairs(profiles) do + if type(v) == "table" then + local label = UI.createWidget("ProfileLabel", SuppliesWindow.profiles) + label:setText(k) + label:setTooltip("Click to load this profile. \nDouble click to change the name.") + label.remove.onClick = function() + local childs = SuppliesWindow.profiles:getChildCount() + if childs == 1 then + return info("vBot[Supplies] You need at least one profile!") + end + profiles[k] = nil + label:destroy() + vBotConfigSave("supply") + end + label.onDoubleClick = function(widget) + local window = modules.client_textedit.show(widget, {title = "Set Profile Name", description = "Enter a new name for selected profile"}) + schedule(50, function() + window:raise() + window:focus() + end) + end + label.onClick = function() + SuppliesConfig[panelName].currentProfile = label:getText() + config = SuppliesConfig[panelName][label:getText()] + loadVariables() + setValues() + vBotConfigSave("supply") + end + label.onTextChange = function(widget,text) + profiles[text] = profiles[k] + profiles[k] = nil + vBotConfigSave("supply") + end + end + end + end + refreshProfileList() + + local function setProfileFocus() + for i,v in ipairs(SuppliesWindow.profiles:getChildren()) do + local name = v:getText() + if name == SuppliesConfig[panelName].currentProfile then + return v:focus() + end + end + end + setProfileFocus() + + SuppliesWindow.newProfile.onClick = function() + local n = SuppliesWindow.profiles:getChildCount() + if n > 6 then + return info("vBot[Supplies] - max profile count reached!") + end + local name = "Profile #"..n+1 + SuppliesConfig[panelName][name] = {} + refreshProfileList() + setProfileFocus() + vBotConfigSave("supply") + end + + SuppliesWindow.capSwitch.onClick = function(widget) + config.capSwitch = not config.capSwitch + widget:setOn(config.capSwitch) + end + + SuppliesWindow.SoftBoots.onClick = function(widget) + config.SoftBoots = not config.SoftBoots + widget:setOn(config.SoftBoots) + end + + SuppliesWindow.imbues.onClick = function(widget) + config.imbues = not config.imbues + widget:setOn(config.imbues) + end + + SuppliesWindow.staminaSwitch.onClick = function(widget) + config.staminaSwitch = not config.staminaSwitch + widget:setOn(config.staminaSwitch) + end + + -- bot items + + SuppliesWindow.item1.onItemChange = function(widget) + config.item1 = widget:getItemId() + end + + SuppliesWindow.item2.onItemChange = function(widget) + config.item2 = widget:getItemId() + end + + SuppliesWindow.item3.onItemChange = function(widget) + config.item3 = widget:getItemId() + end + + SuppliesWindow.item4.onItemChange = function(widget) + config.item4 = widget:getItemId() + end + + SuppliesWindow.item5.onItemChange = function(widget) + config.item5 = widget:getItemId() + end + + SuppliesWindow.item6:setItemId(config.item6) + SuppliesWindow.item6.onItemChange = function(widget) + config.item6 = widget:getItemId() + end + + -- text windows + SuppliesWindow.capValue.onTextChange = function(widget, text) + local value = tonumber(SuppliesWindow.capValue:getText()) + if not value then + SuppliesWindow.capValue:setText(0) + config.capValue = 0 + else + text = text:match("0*(%d+)") + config.capValue = text + end + end + + SuppliesWindow.item1Min.onTextChange = function(widget, text) + local value = tonumber(SuppliesWindow.item1Min:getText()) + if not value then + SuppliesWindow.item1Min:setText(0) + config.item1Min = 0 + else + text = text:match("0*(%d+)") + config.item1Min = text + end + end + + SuppliesWindow.item1Max.onTextChange = function(widget, text) + local value = tonumber(SuppliesWindow.item1Max:getText()) + if not value then + SuppliesWindow.item1Max:setText(0) + config.item1Max = 0 + else + text = text:match("0*(%d+)") + config.item1Max = text + end + end + + SuppliesWindow.item2Min.onTextChange = function(widget, text) + local value = tonumber(SuppliesWindow.item2Min:getText()) + if not value then + SuppliesWindow.item2Min:setText(0) + config.item2Min = 0 + else + text = text:match("0*(%d+)") + config.item2Min = text + end + end + + SuppliesWindow.item2Max.onTextChange = function(widget, text) + local value = tonumber(SuppliesWindow.item2Max:getText()) + if not value then + SuppliesWindow.item2Max:setText(0) + config.item2Max = 0 + else + text = text:match("0*(%d+)") + config.item2Max = text + end + end + + SuppliesWindow.item3Min.onTextChange = function(widget, text) + local value = tonumber(SuppliesWindow.item3Min:getText()) + if not value then + SuppliesWindow.item3Min:setText(0) + config.item3Min = 0 + else + text = text:match("0*(%d+)") + config.item3Min = text + end + end + + SuppliesWindow.item3Max.onTextChange = function(widget, text) + local value = tonumber(SuppliesWindow.item3Max:getText()) + if not value then + SuppliesWindow.item3Max:setText(0) + config.item3Max = 0 + else + config.item3Max = text + end + end + + SuppliesWindow.item4Min.onTextChange = function(widget, text) + local value = tonumber(SuppliesWindow.item4Min:getText()) + if not value then + SuppliesWindow.item4Min:setText(0) + config.item4Min = 0 + else + text = text:match("0*(%d+)") + config.item4Min = text + end + end + + SuppliesWindow.staminaValue.onTextChange = function(widget, text) + local value = tonumber(SuppliesWindow.staminaValue:getText()) + if not value then + SuppliesWindow.staminaValue:setText(0) + config.staminaValue = 0 + else + text = text:match("0*(%d+)") + config.staminaValue = text + end + end + + SuppliesWindow.item4Max.onTextChange = function(widget, text) + local value = tonumber(SuppliesWindow.item4Max:getText()) + if not value then + SuppliesWindow.item4Max:setText(0) + config.item4Max = 0 + else + text = text:match("0*(%d+)") + config.item4Max = text + end + end + + SuppliesWindow.item5Min.onTextChange = function(widget, text) + local value = tonumber(SuppliesWindow.item5Min:getText()) + if not value then + SuppliesWindow.item5Min:setText(0) + config.item5Min = 0 + else + text = text:match("0*(%d+)") + config.item5Min = text + end + end + + SuppliesWindow.item5Max.onTextChange = function(widget, text) + local value = tonumber(SuppliesWindow.item5Max:getText()) + if not value then + SuppliesWindow.item5Max:setText(0) + config.item5Max = 0 + else + text = text:match("0*(%d+)") + config.item5Max = text + end + end + + SuppliesWindow.item6Min.onTextChange = function(widget, text) + local value = tonumber(SuppliesWindow.item6Min:getText()) + if not value then + SuppliesWindow.item6Min:setText(0) + config.item6Min = 0 + else + text = text:match("0*(%d+)") + config.item6Min = text + end + end + + SuppliesWindow.item6Max.onTextChange = function(widget, text) + local value = tonumber(SuppliesWindow.item6Max:getText()) + if not value then + SuppliesWindow.item6Max:setText(0) + config.item6Max = 0 + else + text = text:match("0*(%d+)") + config.item6Max = text + end + end + + Supplies = {} + Supplies.show = function() + SuppliesWindow:show() + SuppliesWindow:raise() + SuppliesWindow:focus() + end +end + +UI.Button("Supplies", function() + SuppliesWindow:show() + SuppliesWindow:raise() + SuppliesWindow:focus() +end) + +SuppliesWindow.close.onClick = function(widget) + SuppliesWindow:hide() +end +end + +UI.Separator() +SuppliesPanel(setDefaultTab("Cave")) \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/supplies.otui b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/supplies.otui new file mode 100644 index 0000000..7f86eab --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/supplies.otui @@ -0,0 +1,361 @@ +ProfileLabel < UIWidget + background-color: alpha + text-offset: 3 1 + focusable: true + height: 16 + font: verdana-11px-rounded + text-align: left + + $focus: + background-color: #00000055 + + Button + id: remove + !text: tr('X') + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + width: 14 + height: 14 + margin-right: 3 + text-align: center + text-offset: 0 1 + tooltip: Remove profile from the list. + +SuppliesWindow < MainWindow + !text: tr('Supplies') + size: 430 310 + @onEscape: self:hide() + + VerticalSeparator + id: sep + anchors.top: parent.top + anchors.left: item1Max.right + anchors.bottom: parent.bottom + margin-top: 3 + margin-bottom: 3 + margin-left: 10 + + Label + anchors.left: sep.right + anchors.right: parent.right + anchors.top: parent.top + margin-left: 10 + margin-top: 3 + text-align: center + text: Additional Conditions: + + HorizontalSeparator + anchors.top: prev.bottom + anchors.left: prev.left + anchors.right: prev.right + margin-top: 3 + + BotSwitch + id: SoftBoots + anchors.top: prev.bottom + anchors.left: sep.right + anchors.right: parent.right + margin-top: 5 + margin-left: 10 + text: No Soft + tooltip: Go refill if there's no more active soft boots. + + BotSwitch + id: capSwitch + height: 20 + anchors.left: SoftBoots.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 5 + margin-right: 50 + text-align: center + text: Cap Below: + tooltip: Go refill if capacity is below set value. + + BotTextEdit + id: capValue + size: 40 20 + anchors.left: prev.right + anchors.right: parent.right + anchors.top: prev.top + margin-left: 5 + + BotSwitch + id: staminaSwitch + height: 20 + anchors.left: SoftBoots.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 5 + margin-right: 50 + text-align: center + text: Stamina: + tooltip: Go refill if stamina is below set value. (in minutes) + + BotTextEdit + id: staminaValue + size: 40 20 + anchors.left: prev.right + anchors.right: parent.right + anchors.top: prev.top + margin-left: 5 + + BotSwitch + id: imbues + anchors.top: prev.bottom + anchors.left: sep.right + anchors.right: parent.right + margin-top: 5 + margin-left: 10 + text: No Imbues + tooltip: Go refill when mana leech imbue has worn off. + + TextList + id: profiles + anchors.top: prev.bottom + margin-top: 5 + anchors.left: prev.left + anchors.right: prev.right + anchors.bottom: close.top + margin-bottom: 20 + + BotButton + id: newProfile + anchors.left: prev.left + anchors.top: prev.bottom + size: 35 15 + text: New + font: cipsoftFont + tooltip: Create new supplies profile. + + BotItem + id: item1 + anchors.left: parent.left + anchors.top: parent.top + margin-top: 3 + + Label + id: MinLabel + !text: tr('Min Amount') + anchors.left: prev.right + anchors.top: prev.top + margin-left: 18 + + Label + id: MaxLabel + !text: tr('Max Amount') + anchors.left: prev.right + anchors.top: prev.top + margin-left: 35 + + BotTextEdit + id: item1Min + size: 100 20 + anchors.left: parent.left + anchors.top: prev.top + margin-top: 15 + margin-left: 40 + text-align: center + + BotTextEdit + id: item1Max + size: 100 20 + anchors.left: prev.right + anchors.top: prev.top + margin-left: 5 + text-align: center + + BotItem + id: item2 + anchors.left: parent.left + anchors.top: prev.top + margin-top: 30 + + Label + id: MinLabel + !text: tr('Min Amount') + anchors.left: prev.right + anchors.top: prev.top + margin-left: 18 + + Label + id: MaxLabel + !text: tr('Max Amount') + anchors.left: prev.right + anchors.top: prev.top + margin-left: 35 + + BotTextEdit + id: item2Min + size: 100 20 + anchors.left: parent.left + anchors.top: prev.top + margin-top: 15 + margin-left: 40 + text-align: center + + BotTextEdit + id: item2Max + size: 100 20 + anchors.left: prev.right + anchors.top: prev.top + margin-left: 5 + text-align: center + + BotItem + id: item3 + anchors.left: parent.left + anchors.top: prev.top + margin-top: 30 + + Label + id: MinLabel + !text: tr('Min Amount') + anchors.left: prev.right + anchors.top: prev.top + margin-left: 18 + + Label + id: MaxLabel + !text: tr('Max Amount') + anchors.left: prev.right + anchors.top: prev.top + margin-left: 35 + + BotTextEdit + id: item3Min + size: 100 20 + anchors.left: parent.left + anchors.top: prev.top + margin-top: 15 + margin-left: 40 + text-align: center + + BotTextEdit + id: item3Max + size: 100 20 + anchors.left: prev.right + anchors.top: prev.top + margin-left: 5 + text-align: center + + BotItem + id: item4 + anchors.left: parent.left + anchors.top: prev.top + margin-top: 30 + + Label + id: MinLabel + !text: tr('Min Amount') + anchors.left: prev.right + anchors.top: prev.top + margin-left: 18 + + Label + id: MaxLabel + !text: tr('Max Amount') + anchors.left: prev.right + anchors.top: prev.top + margin-left: 35 + + BotTextEdit + id: item4Min + size: 100 20 + anchors.left: parent.left + anchors.top: prev.top + margin-top: 15 + margin-left: 40 + text-align: center + + BotTextEdit + id: item4Max + size: 100 20 + anchors.left: prev.right + anchors.top: prev.top + margin-left: 5 + text-align: center + + BotItem + id: item5 + anchors.left: parent.left + anchors.top: prev.top + margin-top: 30 + + Label + id: MinLabel + !text: tr('Min Amount') + anchors.left: prev.right + anchors.top: prev.top + margin-left: 18 + + Label + id: MaxLabel + !text: tr('Max Amount') + anchors.left: prev.right + anchors.top: prev.top + margin-left: 35 + + BotTextEdit + id: item5Min + size: 100 20 + anchors.left: parent.left + anchors.top: prev.top + margin-top: 15 + margin-left: 40 + text-align: center + + BotTextEdit + id: item5Max + size: 100 20 + anchors.left: prev.right + anchors.top: prev.top + margin-left: 5 + text-align: center + + BotItem + id: item6 + anchors.left: parent.left + anchors.top: prev.top + margin-top: 30 + + Label + id: MinLabel + !text: tr('Min Amount') + anchors.left: prev.right + anchors.top: prev.top + margin-left: 18 + + Label + id: MaxLabel + !text: tr('Max Amount') + anchors.left: prev.right + anchors.top: prev.top + margin-left: 35 + + BotTextEdit + id: item6Min + size: 100 20 + anchors.left: parent.left + anchors.top: prev.top + margin-top: 15 + margin-left: 40 + text-align: center + + BotTextEdit + id: item6Max + size: 100 20 + anchors.left: prev.right + anchors.top: prev.top + margin-left: 5 + text-align: center + + Button + id: close + !text: tr('Close') + font: cipsoftFont + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 45 21 + margin-top: 15 + tooltip: Close supplies window and save settings. \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/tools.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/tools.lua new file mode 100644 index 0000000..6105692 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/tools.lua @@ -0,0 +1,46 @@ +-- tools tab +setDefaultTab("Tools") + +if type(storage.moneyItems) ~= "table" then + storage.moneyItems = {3031, 3035} +end +macro(1000, "Exchange money", function() + if not storage.moneyItems[1] then return end + local containers = g_game.getContainers() + for index, container in pairs(containers) do + if not container.lootContainer then -- ignore monster containers + for i, item in ipairs(container:getItems()) do + if item:getCount() == 100 then + for m, moneyId in ipairs(storage.moneyItems) do + if item:getId() == moneyId.id then + return g_game.use(item) + end + end + end + end + end + end +end) + +local moneyContainer = UI.Container(function(widget, items) + storage.moneyItems = items +end, true) +moneyContainer:setHeight(35) +moneyContainer:setItems(storage.moneyItems) + +UI.Separator() + +macro(60000, "Send message on trade", function() + local trade = getChannelId("advertising") + if not trade then + trade = getChannelId("trade") + end + if trade and storage.autoTradeMessage:len() > 0 then + sayChannel(trade, storage.autoTradeMessage) + end +end) +UI.TextEdit(storage.autoTradeMessage or "I'm using OTClientV8!", function(widget, text) + storage.autoTradeMessage = text +end) + +UI.Separator() diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/version.txt b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/version.txt new file mode 100644 index 0000000..958d30d --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/version.txt @@ -0,0 +1 @@ +4.5 \ No newline at end of file diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/vlib.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/vlib.lua new file mode 100644 index 0000000..d92c57f --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/vlib.lua @@ -0,0 +1,1208 @@ +-- Author: Vithrax +-- contains mostly basic function shortcuts and code shorteners + +-- initial global variables declaration +vBot = {} -- global namespace for bot variables +vBot.BotServerMembers = {} +vBot.standTime = now +vBot.isUsingPotion = false +vBot.isUsing = false +vBot.customCooldowns = {} + +-- scripts / functions +onPlayerPositionChange(function(x,y) + vBot.standTime = now +end) + +function standTime() + return now - vBot.standTime +end + +function relogOnCharacter(charName) + local characters = g_ui.getRootWidget().charactersWindow.characters + for index, child in ipairs(characters:getChildren()) do + local name = child:getChildren()[1]:getText() + + if name:lower():find(charName:lower()) then + child:focus() + schedule(100, modules.client_entergame.CharacterList.doLogin) + end + end +end + +function castSpell(text) + if canCast(text) then + say(text) + end +end + +local dmgTable = {} +local lastDmgMessage = now +onTextMessage(function(mode, text) + if not text:lower():find("you lose") or not text:lower():find("due to") then + return + end + local dmg = string.match(text, "%d+") + if #dmgTable > 0 then + for k, v in ipairs(dmgTable) do + if now - v.t > 3000 then table.remove(dmgTable, k) end + end + end + lastDmgMessage = now + table.insert(dmgTable, {d = dmg, t = now}) + schedule(3050, function() + if now - lastDmgMessage > 3000 then dmgTable = {} end + end) +end) + +-- based on data collected by callback calculates per second damage +-- returns number +function burstDamageValue() + local d = 0 + local time = 0 + if #dmgTable > 1 then + for i, v in ipairs(dmgTable) do + if i == 1 then time = v.t end + d = d + v.d + end + end + return math.ceil(d / ((now - time) / 1000)) +end + +-- simplified function from modules +-- displays string as white colour message +function whiteInfoMessage(text) + return modules.game_textmessage.displayGameMessage(text) +end + +function statusMessage(text, logInConsole) + return not logInConsole and modules.game_textmessage.displayFailureMessage(text) or modules.game_textmessage.displayStatusMessage(text) +end + +-- same as above but red message +function broadcastMessage(text) + return modules.game_textmessage.displayBroadcastMessage(text) +end + +-- almost every talk action inside cavebot has to be done by using schedule +-- therefore this is simplified function that doesn't require to build a body for schedule function +function scheduleNpcSay(text, delay) + if not text or not delay then return false end + + return schedule(delay, function() NPC.say(text) end) +end + +-- returns first number in string, already formatted as number +-- returns number or nil +function getFirstNumberInText(text) + local n = nil + if string.match(text, "%d+") then n = tonumber(string.match(text, "%d+")) end + return n +end + +-- function to search if item of given ID can be found on certain tile +-- first argument is always ID +-- the rest of aguments can be: +-- - tile +-- - position +-- - or x,y,z coordinates as p1, p2 and p3 +-- returns boolean +function isOnTile(id, p1, p2, p3) + if not id then return end + local tile + if type(p1) == "table" then + tile = g_map.getTile(p1) + elseif type(p1) ~= "number" then + tile = p1 + else + local p = getPos(p1, p2, p3) + tile = g_map.getTile(p) + end + if not tile then return end + + local item = false + if #tile:getItems() ~= 0 then + for i, v in ipairs(tile:getItems()) do + if v:getId() == id then item = true end + end + else + return false + end + + return item +end + +-- position is a special table, impossible to compare with normal one +-- this is translator from x,y,z to proper position value +-- returns position table +function getPos(x, y, z) + if not x or not y or not z then return nil end + local pos = pos() + pos.x = x + pos.y = y + pos.z = z + + return pos +end + +-- opens purse... that's it +function openPurse() + return g_game.use(g_game.getLocalPlayer():getInventoryItem( + InventorySlotPurse)) +end + +-- check's whether container is full +-- c has to be container object +-- returns boolean +function containerIsFull(c) + if not c then return false end + + if c:getCapacity() > #c:getItems() then + return false + else + return true + end + +end + +function dropItem(idOrObject) + if type(idOrObject) == "number" then + idOrObject = findItem(idOrObject) + end + + g_game.move(idOrObject, pos(), idOrObject:getCount()) +end + +-- not perfect function to return whether character has utito tempo buff +-- known to be bugged if received debuff (ie. roshamuul) +-- TODO: simply a better version +-- returns boolean +function isBuffed() + local var = false + if not hasPartyBuff() then return var end + + local skillId = 0 + for i = 1, 4 do + if player:getSkillBaseLevel(i) > player:getSkillBaseLevel(skillId) then + skillId = i + end + end + + local premium = (player:getSkillLevel(skillId) - player:getSkillBaseLevel(skillId)) + local base = player:getSkillBaseLevel(skillId) + if (premium / 100) * 305 > base then + var = true + end + return var +end + +-- if using index as table element, this can be used to properly assign new idex to all values +-- table needs to contain "index" as value +-- if no index in tables, it will create one +function reindexTable(t) + if not t or type(t) ~= "table" then return end + + local i = 0 + for _, e in pairs(t) do + i = i + 1 + e.index = i + end +end + +-- supports only new tibia, ver 10+ +-- returns how many kills left to get next skull - can be red skull, can be black skull! +-- reutrns number +function killsToRs() + return math.min(g_game.getUnjustifiedPoints().killsDayRemaining, + g_game.getUnjustifiedPoints().killsWeekRemaining, + g_game.getUnjustifiedPoints().killsMonthRemaining) +end + +-- calculates exhaust for potions based on "Aaaah..." message +-- changes state of vBot variable, can be used in other scripts +-- already used in pushmax, healbot, etc + +onTalk(function(name, level, mode, text, channelId, pos) + if name ~= player:getName() then return end + if mode ~= 34 then return end + + if text == "Aaaah..." then + vBot.isUsingPotion = true + schedule(950, function() vBot.isUsingPotion = false end) + end +end) + +-- [[ canCast and cast functions ]] -- +-- callback connected to cast and canCast function +-- detects if a given spell was in fact casted based on player's text messages +-- Cast text and message text must match +-- checks only spells inserted in SpellCastTable by function cast +SpellCastTable = {} +onTalk(function(name, level, mode, text, channelId, pos) + if name ~= player:getName() then return end + text = text:lower() + + if SpellCastTable[text] then SpellCastTable[text].t = now end +end) + +-- if delay is nil or delay is lower than 100 then this function will act as a normal say function +-- checks or adds a spell to SpellCastTable and updates cast time if exist +function cast(text, delay) + text = text:lower() + if type(text) ~= "string" then return end + if not delay or delay < 100 then + return say(text) -- if not added delay or delay is really low then just treat it like casual say + end + if not SpellCastTable[text] or SpellCastTable[text].d ~= delay then + SpellCastTable[text] = {t = now - delay, d = delay} + return say(text) + end + local lastCast = SpellCastTable[text].t + local spellDelay = SpellCastTable[text].d + if now - lastCast > spellDelay then return say(text) end +end + +-- canCast is a base for AttackBot and HealBot +-- checks if spell is ready to be casted again +-- ignoreRL - if true, aparat from cooldown will also check conditions inside gamelib SpellInfo table +-- ignoreCd - it true, will ignore cooldown +-- returns boolean +local Spells = modules.gamelib.SpellInfo['Default'] +function canCast(spell, ignoreRL, ignoreCd) + if type(spell) ~= "string" then return end + spell = spell:lower() + if SpellCastTable[spell] then + if now - SpellCastTable[spell].t > SpellCastTable[spell].d or ignoreCd then + return true + else + return false + end + end + if getSpellData(spell) then + if (ignoreCd or not getSpellCoolDown(spell)) and + (ignoreRL or level() >= getSpellData(spell).level and mana() >= + getSpellData(spell).mana) then + return true + else + return false + end + end + -- if no data nor spell table then return true + return true +end + +local lastPhrase = "" +onTalk(function(name, level, mode, text, channelId, pos) + if name == player:getName() then + lastPhrase = text:lower() + end +end) + +if onSpellCooldown and onGroupSpellCooldown then + onSpellCooldown(function(iconId, duration) + schedule(5, function() + if not vBot.customCooldowns[lastPhrase] then + vBot.customCooldowns[lastPhrase] = {id = iconId} + end + end) + end) + + onGroupSpellCooldown(function(iconId, duration) + schedule(10, function() + if vBot.customCooldowns[lastPhrase] then + vBot.customCooldowns[lastPhrase] = {id = vBot.customCooldowns[lastPhrase].id, group = {[iconId] = duration}} + end + end) + end) +else + warn("Outdated OTClient! update to newest version to take benefits from all scripts!") +end + +-- exctracts data about spell from gamelib SpellInfo table +-- returns table +-- ie:['Spell Name'] = {id, words, exhaustion, premium, type, icon, mana, level, soul, group, vocations} +-- cooldown detection module +function getSpellData(spell) + if not spell then return false end + spell = spell:lower() + local t = nil + local c = nil + for k, v in pairs(Spells) do + if v.words == spell then + t = k + break + end + end + if not t then + for k, v in pairs(vBot.customCooldowns) do + if k == spell then + c = {id = v.id, mana = 1, level = 1, group = v.group} + break + end + end + end + if t then + return Spells[t] + elseif c then + return c + else + return false + end +end + +-- based on info extracted by getSpellData checks if spell is on cooldown +-- returns boolean +function getSpellCoolDown(text) + if not text then return nil end + text = text:lower() + local data = getSpellData(text) + if not data then return false end + local icon = modules.game_cooldown.isCooldownIconActive(data.id) + local group = false + for groupId, duration in pairs(data.group) do + if modules.game_cooldown.isGroupCooldownIconActive(groupId) then + group = true + break + end + end + if icon or group then + return true + else + return false + end +end + +-- global var to indicate that player is trying to do something +-- prevents action blocking by scripts +-- below callbacks are triggers to changing the var state +local isUsingTime = now +macro(100, function() + vBot.isUsing = now < isUsingTime and true or false +end) +onUse(function(pos, itemId, stackPos, subType) + if pos.x > 65000 then return end + if getDistanceBetween(player:getPosition(), pos) > 1 then return end + local tile = g_map.getTile(pos) + if not tile then return end + + local topThing = tile:getTopUseThing() + if topThing:isContainer() then return end + + isUsingTime = now + 1000 +end) +onUseWith(function(pos, itemId, target, subType) + if pos.x < 65000 then isUsingTime = now + 1000 end +end) + +-- returns first word in string +function string.starts(String, Start) + return string.sub(String, 1, string.len(Start)) == Start +end + +-- global tables for cached players to prevent unnecesary resource consumption +-- probably still can be improved, TODO in future +-- c can be creature or string +-- if exected then adds name or name and creature to tables +-- returns boolean +CachedFriends = {} +CachedEnemies = {} +function isFriend(c) + local name = c + if type(c) ~= "string" then + if c == player then return true end + name = c:getName() + end + + if CachedFriends[c] then return true end + if CachedEnemies[c] then return false end + + if table.find(storage.playerList.friendList, name) then + CachedFriends[c] = true + return true + elseif vBot.BotServerMembers[name] ~= nil then + CachedFriends[c] = true + return true + elseif storage.playerList.groupMembers then + local p = c + if type(c) == "string" then p = getCreatureByName(c, true) end + if not p then return false end + if p:isLocalPlayer() then return true end + if p:isPlayer() then + if p:isPartyMember() then + CachedFriends[c] = true + CachedFriends[p] = true + return true + end + end + else + return false + end +end + +-- similar to isFriend but lighter version +-- accepts only name string +-- returns boolean +function isEnemy(c) + local name = c + local p + if type(c) ~= "string" then + if c == player then return false end + name = c:getName() + p = c + end + if not name then return false end + if not p then + p = getCreatureByName(name, true) + end + if not p then return end + if p:isLocalPlayer() then return end + + if p:isPlayer() and table.find(storage.playerList.enemyList, name) or + (storage.playerList.marks and not isFriend(name)) or p:getEmblem() == 2 then + return true + else + return false + end +end + +function getPlayerDistribution() + local friends = {} + local neutrals = {} + local enemies = {} + for i, spec in ipairs(getSpectators()) do + if spec:isPlayer() and not spec:isLocalPlayer() then + if isFriend(spec) then + table.insert(friends, spec) + elseif isEnemy(spec) then + table.insert(enemies, spec) + else + table.insert(neutrals, spec) + end + end + end + + return friends, neutrals, enemies +end + +function getFriends() + local friends, neutrals, enemies = getPlayerDistribution() + + return friends +end + +function getNeutrals() + local friends, neutrals, enemies = getPlayerDistribution() + + return neutrals +end + +function getEnemies() + local friends, neutrals, enemies = getPlayerDistribution() + + return enemies +end + +-- based on first word in string detects if text is a offensive spell +-- returns boolean +function isAttSpell(expr) + if string.starts(expr, "exori") or string.starts(expr, "exevo") then + return true + else + return false + end +end + +-- returns dressed-up item id based on not dressed id +-- returns number +function getActiveItemId(id) + if not id then return false end + + if id == 3049 then + return 3086 + elseif id == 3050 then + return 3087 + elseif id == 3051 then + return 3088 + elseif id == 3052 then + return 3089 + elseif id == 3053 then + return 3090 + elseif id == 3091 then + return 3094 + elseif id == 3092 then + return 3095 + elseif id == 3093 then + return 3096 + elseif id == 3097 then + return 3099 + elseif id == 3098 then + return 3100 + elseif id == 16114 then + return 16264 + elseif id == 23531 then + return 23532 + elseif id == 23533 then + return 23534 + elseif id == 23529 then + return 23530 + elseif id == 30343 then -- Sleep Shawl + return 30342 + elseif id == 30344 then -- Enchanted Pendulet + return 30345 + elseif id == 30403 then -- Enchanted Theurgic Amulet + return 30402 + elseif id == 31621 then -- Blister Ring + return 31616 + elseif id == 32621 then -- Ring of Souls + return 32635 + else + return id + end +end + +-- returns not dressed item id based on dressed-up id +-- returns number +function getInactiveItemId(id) + if not id then return false end + + if id == 3086 then + return 3049 + elseif id == 3087 then + return 3050 + elseif id == 3088 then + return 3051 + elseif id == 3089 then + return 3052 + elseif id == 3090 then + return 3053 + elseif id == 3094 then + return 3091 + elseif id == 3095 then + return 3092 + elseif id == 3096 then + return 3093 + elseif id == 3099 then + return 3097 + elseif id == 3100 then + return 3098 + elseif id == 16264 then + return 16114 + elseif id == 23532 then + return 23531 + elseif id == 23534 then + return 23533 + elseif id == 23530 then + return 23529 + elseif id == 30342 then -- Sleep Shawl + return 30343 + elseif id == 30345 then -- Enchanted Pendulet + return 30344 + elseif id == 30402 then -- Enchanted Theurgic Amulet + return 30403 + elseif id == 31616 then -- Blister Ring + return 31621 + elseif id == 32635 then -- Ring of Souls + return 32621 + else + return id + end +end + +-- returns amount of monsters within the range of position +-- does not include summons (new tibia) +-- returns number +function getMonstersInRange(pos, range) + if not pos or not range then return false end + local monsters = 0 + for i, spec in pairs(getSpectators()) do + if spec:isMonster() and + (g_game.getClientVersion() < 960 or spec:getType() < 3) and + getDistanceBetween(pos, spec:getPosition()) < range then + monsters = monsters + 1 + end + end + return monsters +end + +-- shortcut in calculating distance from local player position +-- needs only one argument +-- returns number +function distanceFromPlayer(coords) + if not coords then return false end + return getDistanceBetween(pos(), coords) +end + +-- returns amount of monsters within the range of local player position +-- does not include summons (new tibia) +-- can also check multiple floors +-- returns number +function getMonsters(range, multifloor) + if not range then range = 10 end + local mobs = 0; + for _, spec in pairs(getSpectators(multifloor)) do + mobs = (g_game.getClientVersion() < 960 or spec:getType() < 3) and + spec:isMonster() and distanceFromPlayer(spec:getPosition()) <= + range and mobs + 1 or mobs; + end + return mobs; +end + +-- returns amount of players within the range of local player position +-- does not include party members +-- can also check multiple floors +-- returns number +function getPlayers(range, multifloor) + if not range then range = 10 end + local specs = 0; + for _, spec in pairs(getSpectators(multifloor)) do + specs = not spec:isLocalPlayer() and spec:isPlayer() and + distanceFromPlayer(spec:getPosition()) <= range and + not (spec:isPartyMember() or spec:getEmblem() == 1) and specs + 1 or specs; + end + return specs; +end + +-- this is multifloor function +-- checks if player added in "Anti RS list" in player list is within the given range +-- returns boolean +function isBlackListedPlayerInRange(range) + if #storage.playerList.blackList == 0 then return end + if not range then range = 10 end + local found = false + for _, spec in pairs(getSpectators(true)) do + local specPos = spec:getPosition() + local pPos = player:getPosition() + if spec:isPlayer() then + if math.abs(specPos.z - pPos.z) <= 2 then + if specPos.z ~= pPos.z then specPos.z = pPos.z end + if distanceFromPlayer(specPos) < range then + if table.find(storage.playerList.blackList, spec:getName()) then + found = true + end + end + end + end + end + return found +end + +-- checks if there is non-friend player withing the range +-- padding is only for multifloor +-- returns boolean +function isSafe(range, multifloor, padding) + local onSame = 0 + local onAnother = 0 + if not multifloor and padding then + multifloor = false + padding = false + end + + for _, spec in pairs(getSpectators(multifloor)) do + if spec:isPlayer() and not spec:isLocalPlayer() and + not isFriend(spec:getName()) then + if spec:getPosition().z == posz() and + distanceFromPlayer(spec:getPosition()) <= range then + onSame = onSame + 1 + end + if multifloor and padding and spec:getPosition().z ~= posz() and + distanceFromPlayer(spec:getPosition()) <= (range + padding) then + onAnother = onAnother + 1 + end + end + end + + if onSame + onAnother > 0 then + return false + else + return true + end +end + +-- returns amount of players within the range of local player position +-- can also check multiple floors +-- returns number +function getAllPlayers(range, multifloor) + if not range then range = 10 end + local specs = 0; + for _, spec in pairs(getSpectators(multifloor)) do + specs = not spec:isLocalPlayer() and spec:isPlayer() and + distanceFromPlayer(spec:getPosition()) <= range and specs + + 1 or specs; + end + return specs; +end + +-- returns amount of NPC's within the range of local player position +-- can also check multiple floors +-- returns number +function getNpcs(range, multifloor) + if not range then range = 10 end + local npcs = 0; + for _, spec in pairs(getSpectators(multifloor)) do + npcs = + spec:isNpc() and distanceFromPlayer(spec:getPosition()) <= range and + npcs + 1 or npcs; + end + return npcs; +end + +-- main function for calculatin item amount in all visible containers +-- also considers equipped items +-- returns number +function itemAmount(id) + local totalItemCount = 0 + for _, container in pairs(getContainers()) do + if not container:getName():lower():find("depot") and not container:getName():lower():find("your inbox") then + for _, item in ipairs(container:getItems()) do + totalItemCount = item:getId() == id and totalItemCount + + item:getCount() or totalItemCount + end + end + end + + local slots = {getHead(), getNeck(), getBack(), getBody(), getRight(), getLeft(), getLeg(), getFeet(), getFinger(), getAmmo()} + for i, slot in pairs(slots) do + totalItemCount = slot and slot:getId() == id and totalItemCount + 1 or totalItemCount + end + return totalItemCount +end + +-- self explanatory +-- a is item to use on +-- b is item to use a on +function useOnInvertoryItem(a, b) + local item = findItem(b) + if not item then return end + + return useWith(a, item) +end + +-- checks if player has at least 50% of minimal supplies given in Supplies window +-- returns boolean +function hasSupplies() + local supplies = SuppliesConfig.supplies + supplies = supplies[supplies.currentProfile] + local items = { + {ID = supplies.item1, minAmount = supplies.item1Min}, + {ID = supplies.item2, minAmount = supplies.item2Min}, + {ID = supplies.item3, minAmount = supplies.item3Min}, + {ID = supplies.item4, minAmount = supplies.item4Min}, + {ID = supplies.item5, minAmount = supplies.item5Min}, + {ID = supplies.item6, minAmount = supplies.item6Min}, + {ID = supplies.item7, minAmount = supplies.item7Min} + } + -- false = no supplies + -- true = supplies available + + local hasSupplies = true + + for i, supply in pairs(items) do + if supply.minAmount and supply.ID then + if supply.ID > 100 and itemAmount(supply.ID) < + (supply.minAmount / 2) then hasSupplies = false end + end + end + + return hasSupplies +end + +-- pos can be tile or position +-- returns table of tiles surrounding given POS/tile +function getNearTiles(pos) + if type(pos) ~= "table" then pos = pos:getPosition() end + + local tiles = {} + local dirs = { + {-1, 1}, {0, 1}, {1, 1}, {-1, 0}, {1, 0}, {-1, -1}, {0, -1}, {1, -1} + } + for i = 1, #dirs do + local tile = g_map.getTile({ + x = pos.x - dirs[i][1], + y = pos.y - dirs[i][2], + z = pos.z + }) + if tile then table.insert(tiles, tile) end + end + + return tiles +end + +-- self explanatory +-- use along with delay, it will only call action +function useGroundItem(id) + if not id then return false end + + local dest = nil + for i, tile in ipairs(g_map.getTiles(posz())) do + for j, item in ipairs(tile:getItems()) do + if item:getId() == id then + dest = item + break + end + end + end + + if dest then + return use(dest) + else + return false + end +end + +-- self explanatory +-- use along with delay, it will only call action +function reachGroundItem(id) + if not id then return false end + + local dest = nil + for i, tile in ipairs(g_map.getTiles(posz())) do + for j, item in ipairs(tile:getItems()) do + local iPos = item:getPosition() + local iId = item:getId() + if iId == id then + if findPath(pos(), iPos, 20, + {ignoreNonPathable = true, precision = 1}) then + dest = item + break + end + end + end + end + + if dest then + return autoWalk(iPos, 20, {ignoreNonPathable = true, precision = 1}) + else + return false + end +end + +-- self explanatory +-- returns object +function findItemOnGround(id) + for i, tile in ipairs(g_map.getTiles(posz())) do + for j, item in ipairs(tile:getItems()) do + if item:getId() == id then return item end + end + end +end + +-- self explanatory +-- use along with delay, it will only call action +function useOnGroundItem(a, b) + if not b then return false end + local item = findItem(a) + if not item then return false end + + local dest = nil + for i, tile in ipairs(g_map.getTiles(posz())) do + for j, item in ipairs(tile:getItems()) do + if item:getId() == id then + dest = item + break + end + end + end + + if dest then + return useWith(item, dest) + else + return false + end +end + +-- returns target creature +function target() + if not g_game.isAttacking() then + return + else + return g_game.getAttackingCreature() + end +end + +-- returns target creature +function getTarget() return target() end + +-- dist is boolean +-- returns target position/distance from player +function targetPos(dist) + if not g_game.isAttacking() then return end + if dist then + return distanceFromPlayer(target():getPosition()) + else + return target():getPosition() + end +end + +-- for gunzodus/ezodus only +-- it will reopen loot bag, necessary for depositer +function reopenPurse() + for i, c in pairs(getContainers()) do + if c:getName():lower() == "loot bag" or c:getName():lower() == + "store inbox" then g_game.close(c) end + end + schedule(100, function() + g_game.use(g_game.getLocalPlayer():getInventoryItem(InventorySlotPurse)) + end) + schedule(1400, function() + for i, c in pairs(getContainers()) do + if c:getName():lower() == "store inbox" then + for _, i in pairs(c:getItems()) do + if i:getId() == 23721 then + g_game.open(i, c) + end + end + end + end + end) + return CaveBot.delay(1500) +end + +-- getSpectator patterns +-- param1 - pos/creature +-- param2 - pattern +-- param3 - type of return +-- 1 - everyone, 2 - monsters, 3 - players +-- returns number +function getCreaturesInArea(param1, param2, param3) + local specs = 0 + local monsters = 0 + local players = 0 + for i, spec in pairs(getSpectators(param1, param2)) do + if spec ~= player then + specs = specs + 1 + if spec:isMonster() and + (g_game.getClientVersion() < 960 or spec:getType() < 3) then + monsters = monsters + 1 + elseif spec:isPlayer() and not isFriend(spec:getName()) then + players = players + 1 + end + end + end + + if param3 == 1 then + return specs + elseif param3 == 2 then + return monsters + else + return players + end +end + +-- can be improved +-- TODO in future +-- uses getCreaturesInArea, specType +-- returns number +function getBestTileByPatern(pattern, specType, maxDist, safe) + if not pattern or not specType then return end + if not maxDist then maxDist = 4 end + + local bestTile = nil + local best = nil + for _, tile in pairs(g_map.getTiles(posz())) do + if distanceFromPlayer(tile:getPosition()) <= maxDist then + local minimapColor = g_map.getMinimapColor(tile:getPosition()) + local stairs = (minimapColor >= 210 and minimapColor <= 213) + if tile:canShoot() and tile:isWalkable() then + if getCreaturesInArea(tile:getPosition(), pattern, specType) > 0 then + if (not safe or + getCreaturesInArea(tile:getPosition(), pattern, 3) == 0) then + local candidate = + { + pos = tile, + count = getCreaturesInArea(tile:getPosition(), + pattern, specType) + } + if not best or best.count <= candidate.count then + best = candidate + end + end + end + end + end + end + + bestTile = best + + if bestTile then + return bestTile + else + return false + end +end + +-- returns container object based on name +function getContainerByName(name, notFull) + if type(name) ~= "string" then return nil end + + local d = nil + for i, c in pairs(getContainers()) do + if c:getName():lower() == name:lower() and (not notFull or not containerIsFull(c)) then + d = c + break + end + end + return d +end + +-- returns container object based on container ID +function getContainerByItem(id, notFull) + if type(id) ~= "number" then return nil end + + local d = nil + for i, c in pairs(getContainers()) do + if c:getContainerItem():getId() == id and (not notFull or not containerIsFull(c)) then + d = c + break + end + end + return d +end + +-- [[ ready to use getSpectators patterns ]] -- +LargeUeArea = [[ + 0000001000000 + 0000011100000 + 0000111110000 + 0001111111000 + 0011111111100 + 0111111111110 + 1111111111111 + 0111111111110 + 0011111111100 + 0001111111000 + 0000111110000 + 0000011100000 + 0000001000000 +]] + +NormalUeAreaMs = [[ + 00000100000 + 00011111000 + 00111111100 + 01111111110 + 01111111110 + 11111111111 + 01111111110 + 01111111110 + 00111111100 + 00001110000 + 00000100000 +]] + +NormalUeAreaEd = [[ + 00000100000 + 00001110000 + 00011111000 + 00111111100 + 01111111110 + 11111111111 + 01111111110 + 00111111100 + 00011111000 + 00001110000 + 00000100000 +]] + +smallUeArea = [[ + 0011100 + 0111110 + 1111111 + 1111111 + 1111111 + 0111110 + 0011100 +]] + +largeRuneArea = [[ + 0011100 + 0111110 + 1111111 + 1111111 + 1111111 + 0111110 + 0011100 +]] + +adjacentArea = [[ + 111 + 101 + 111 +]] + +longBeamArea = [[ + 0000000N0000000 + 0000000N0000000 + 0000000N0000000 + 0000000N0000000 + 0000000N0000000 + 0000000N0000000 + 0000000N0000000 + WWWWWWW0EEEEEEE + 0000000S0000000 + 0000000S0000000 + 0000000S0000000 + 0000000S0000000 + 0000000S0000000 + 0000000S0000000 + 0000000S0000000 +]] + +shortBeamArea = [[ + 00000100000 + 00000100000 + 00000100000 + 00000100000 + 00000100000 + EEEEE0WWWWW + 00000S00000 + 00000S00000 + 00000S00000 + 00000S00000 + 00000S00000 +]] + +newWaveArea = [[ + 000NNNNN000 + 000NNNNN000 + 0000NNN0000 + WW00NNN00EE + WWWW0N0EEEE + WWWWW0EEEEE + WWWW0S0EEEE + WW00SSS00EE + 0000SSS0000 + 000SSSSS000 + 000SSSSS000 +]] + +bigWaveArea = [[ + 0000NNN0000 + 0000NNN0000 + 0000NNN0000 + 00000N00000 + WWW00N00EEE + WWWWW0EEEEE + WWW00S00EEE + 00000S00000 + 0000SSS0000 + 0000SSS0000 + 0000SSS0000 +]] + +smallWaveArea = [[ + 00NNN00 + 00NNN00 + WW0N0EE + WWW0EEE + WW0S0EE + 00SSS00 + 00SSS00 +]] + +diamondArrowArea = [[ + 01110 + 11111 + 11111 + 11111 + 01110 +]] diff --git a/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/xeno_menu.lua b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/xeno_menu.lua new file mode 100644 index 0000000..2583500 --- /dev/null +++ b/800OTClient/modules/game_bot/default_configs/vBot_4.5/vBot/xeno_menu.lua @@ -0,0 +1,30 @@ +modules.game_interface.gameRootPanel.onMouseRelease = function(widget, mousePos, mouseButton) + if mouseButton == 2 then + local child = rootWidget:recursiveGetChildByPos(mousePos) + if child == widget then + local menu = g_ui.createWidget('PopupMenu') + menu:setId("blzMenu") + menu:setGameMenu(true) + menu:addOption('AttackBot', AttackBot.show, "OTCv8") + menu:addOption('HealBot', HealBot.show, "OTCv8") + menu:addOption('Conditions', Conditions.show, "OTCv8") + menu:addSeparator() + menu:addOption('CaveBot', function() + if CaveBot.isOn() then + CaveBot.setOff() + else + CaveBot.setOn() + end + end, CaveBot.isOn() and "ON " or "OFF ") + menu:addOption('TargetBot', function() + if TargetBot.isOn() then + TargetBot.setOff() + else + TargetBot.setOn() + end + end, TargetBot.isOn() and "ON " or "OFF ") + menu:display(mousePos) + return true + end + end +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/edit.otui b/800OTClient/modules/game_bot/edit.otui new file mode 100644 index 0000000..e45ad6e --- /dev/null +++ b/800OTClient/modules/game_bot/edit.otui @@ -0,0 +1,254 @@ +MainWindow + id: editWindow + !text: tr("Config editor & manager") + @onEscape: self:hide() + size: 550 570 + $mobile: + size: 550 240 + + Panel + id: manager + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + height: 152 + + Label + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-auto-resize: true + text-align: center + text-wrap: true + !text: tr("Config Manager\nYou can use config manager to share configs between different machines, especially smartphones. After you configure your config, you can upload it, then you'll get unique hash code which you can use on diffent machinge (for eg. mobile phone) to download it.") + + HorizontalSeparator + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 3 + height: 2 + + Panel + id: upload + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.bottom: parent.bottom + margin-top: 3 + + Label + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-auto-resize: true + text-align: center + text-wrap: true + !text: tr("Upload config") + + Label + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 7 + text-auto-resize: true + text-align: center + text-wrap: true + !text: tr("Select config to upload") + + ComboBox + id: config + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 4 + margin-left: 20 + margin-right: 20 + text-offset: 3 0 + + Button + id: submit + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + !text: tr('Upload config') + margin-top: 4 + margin-left: 40 + margin-right: 40 + @onClick: modules.game_bot.uploadConfig() + + Panel + id: download + anchors.top: prev.top + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.bottom: parent.bottom + + Label + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-auto-resize: true + text-align: center + text-wrap: true + !text: tr("Download config") + + Label + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 7 + text-auto-resize: true + text-align: center + text-wrap: true + !text: tr("Enter config hash code") + + TextEdit + id: config + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 4 + margin-left: 20 + margin-right: 20 + + Button + id: submit + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + !text: tr('Download config') + margin-top: 4 + margin-left: 40 + margin-right: 40 + @onClick: modules.game_bot.downloadConfig() + + HorizontalSeparator + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 3 + height: 2 + + Panel + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 5 + height: 330 + $mobile: + visible: false + + Label + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-auto-resize: true + text-align: center + text-wrap: true + !text: tr("Bot configs are stored in:") + + TextEdit + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: prev.bottom + height: 20 + width: 400 + margin-top: 5 + editable: false + !text: g_resources.getWriteDir() .. "bot" + text-align: center + + Button + id: documentationButton + !text: tr('Click here to open bot directory') + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: prev.bottom + margin-top: 5 + width: 250 + @onClick: g_platform.openDir(g_resources.getWriteDir() .. "bot") + + Label + margin-top: 5 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + text-auto-resize: true + text-align: center + text-wrap: true + !text: tr("Every directory in bot directory is treated as different config.\nTo create new config just create new directory.") + + Label + margin-top: 5 + anchors.top: prev.bottom + anchors.horizontalCenter: parent.horizontalCenter + height: 175 + image-source: configs.png + image-fixed-ratio: true + image-size: 500 175 + + Label + margin-top: 3 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + text-auto-resize: true + text-align: center + text-wrap: true + !text: tr("Inside config directory put .lua and .otui files.\nEvery file will be loaded and executed in alphabetical order, .otui first and then .lua.") + + Label + margin-top: 3 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + text-auto-resize: true + text-align: center + text-wrap: true + !text: tr("To reload configs just press On and Off in bot window.\nTo learn more about bot click Tutorials button.") + + Button + !text: tr('Documentation') + anchors.bottom: parent.bottom + anchors.left: parent.left + width: 118 + @onClick: g_platform.openUrl("http://otclient.ovh/bot.php?documentation") + + Button + !text: tr('Tutorials') + anchors.bottom: parent.bottom + anchors.left: prev.right + margin-left: 5 + width: 80 + @onClick: g_platform.openUrl("http://otclient.ovh/bot.php?tutorials") + + Button + !text: tr('Scripts') + anchors.bottom: parent.bottom + anchors.left: prev.right + margin-left: 5 + width: 80 + @onClick: g_platform.openUrl("http://otclient.ovh/bot.php?scripts") + + Button + !text: tr('Forum') + anchors.bottom: parent.bottom + anchors.left: prev.right + margin-left: 5 + width: 80 + @onClick: g_platform.openUrl("http://otclient.ovh/bot.php?forum") + + Button + !text: tr('Discord') + anchors.bottom: parent.bottom + anchors.left: prev.right + margin-left: 5 + width: 80 + @onClick: g_platform.openUrl("http://otclient.ovh/bot.php?discord") + + Button + id: cancelButton + !text: tr('Close') + anchors.bottom: parent.bottom + anchors.right: parent.right + width: 60 + @onClick: self:getParent():hide() diff --git a/800OTClient/modules/game_bot/executor.lua b/800OTClient/modules/game_bot/executor.lua new file mode 100644 index 0000000..0566712 --- /dev/null +++ b/800OTClient/modules/game_bot/executor.lua @@ -0,0 +1,409 @@ +function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, reloadCallback, websockets) + -- load lua and otui files + local configFiles = g_resources.listDirectoryFiles("/bot/" .. config, true, false) + local luaFiles = {} + local uiFiles = {} + for i, file in ipairs(configFiles) do + local ext = file:split(".") + if ext[#ext]:lower() == "lua" then + table.insert(luaFiles, file) + end + if ext[#ext]:lower() == "ui" or ext[#ext]:lower() == "otui" then + table.insert(uiFiles, file) + end + end + + if #luaFiles == 0 then + return error("Config (/bot/" .. config .. ") doesn't have lua files") + end + + -- init bot variables + local context = {} + context.configDir = "/bot/".. config + context.tabs = tabs + context.mainTab = context.tabs:addTab("Main", g_ui.createWidget('BotPanel')).tabPanel.content + context.panel = context.mainTab + context.saveConfig = saveConfigCallback + context.reload = reloadCallback + + context.storage = storage + if context.storage._macros == nil then + context.storage._macros = {} -- active macros + end + + -- websockets, macros, hotkeys, scheduler, icons, callbacks + context._websockets = websockets + context._macros = {} + context._hotkeys = {} + context._scheduler = {} + context._callbacks = { + onKeyDown = {}, + onKeyUp = {}, + onKeyPress = {}, + onTalk = {}, + onTextMessage = {}, + onLoginAdvice = {}, + onAddThing = {}, + onRemoveThing = {}, + onCreatureAppear = {}, + onCreatureDisappear = {}, + onCreaturePositionChange = {}, + onCreatureHealthPercentChange = {}, + onUse = {}, + onUseWith = {}, + onContainerOpen = {}, + onContainerClose = {}, + onContainerUpdateItem = {}, + onMissle = {}, + onAnimatedText = {}, + onStaticText = {}, + onChannelList = {}, + onOpenChannel = {}, + onCloseChannel = {}, + onChannelEvent = {}, + onTurn = {}, + onWalk = {}, + onImbuementWindow = {}, + onModalDialog = {}, + onAttackingCreatureChange = {}, + onManaChange = {}, + onStatesChange = {}, + onAddItem = {}, + onGameEditText = {}, + onGroupSpellCooldown = {}, + onSpellCooldown = {}, + onRemoveItem = {} + } + + -- basic functions & classes + context.print = print + context.pairs = pairs + context.ipairs = ipairs + context.tostring = tostring + context.math = math + context.table = table + context.setmetatable = setmetatable + context.string = string + context.tonumber = tonumber + context.type = type + context.pcall = pcall + context.os = { + time = os.time, + difftime = os.difftime, + date = os.date, + clock = os.clock + } + context.load = function(str) return assert(load(str, nil, nil, context)) end + context.loadstring = context.load + context.assert = assert + context.dofile = function(file) assert(load(g_resources.readFileContents("/bot/" .. config .. "/" .. file), file, nil, context))() end + context.gcinfo = gcinfo + context.tr = tr + context.json = json + context.base64 = base64 + context.regexMatch = regexMatch + context.getDistanceBetween = function(p1, p2) + return math.max(math.abs(p1.x - p2.x), math.abs(p1.y - p2.y)) + end + context.isMobile = g_app.isMobile + context.getVersion = g_app.getVersion + + -- classes + context.g_resources = g_resources + context.g_game = g_game + context.g_map = g_map + context.g_ui = g_ui + context.g_sounds = g_sounds + context.g_window = g_window + context.g_mouse = g_mouse + context.g_things = g_things + context.g_settings = g_settings + context.g_platform = { + openUrl = g_platform.openUrl, + openDir = g_platform.openDir, + } + + context.Item = Item + context.Creature = Creature + context.ThingType = ThingType + context.Effect = Effect + context.Missile = Missile + context.Player = Player + context.Monster = Monster + context.StaticText = StaticText + context.HTTP = HTTP + context.OutputMessage = OutputMessage + context.modules = modules + + -- log functions + context.info = function(text) return msgCallback("info", tostring(text)) end + context.warn = function(text) return msgCallback("warn", tostring(text)) end + context.error = function(text) return msgCallback("error", tostring(text)) end + context.warning = context.warn + + -- init context + context.now = g_clock.millis() + context.time = g_clock.millis() + context.player = g_game.getLocalPlayer() + + -- init functions + G.botContext = context + dofiles("functions") + context.Panels = {} + dofiles("panels") + G.botContext = nil + + -- run ui scripts + for i, file in ipairs(uiFiles) do + g_ui.importStyle(file) + end + + -- run lua script + for i, file in ipairs(luaFiles) do + assert(load(g_resources.readFileContents(file), file, nil, context))() + context.panel = context.mainTab -- reset default tab + end + + return { + script = function() + context.now = g_clock.millis() + context.time = g_clock.millis() + + for i, macro in ipairs(context._macros) do + if macro.lastExecution + macro.timeout <= context.now and macro.enabled then + local status, result = pcall(function() + if macro.callback(macro) then + macro.lastExecution = context.now + end + end) + if not status then + context.error("Macro: " .. macro.name .. " execution error: " .. result) + end + end + end + + while #context._scheduler > 0 and context._scheduler[1].execution <= g_clock.millis() do + local status, result = pcall(function() + context._scheduler[1].callback() + end) + if not status then + context.error("Schedule execution error: " .. result) + end + table.remove(context._scheduler, 1) + end + end, + callbacks = { + onKeyDown = function(keyCode, keyboardModifiers) + local keyDesc = determineKeyComboDesc(keyCode, keyboardModifiers) + for i, macro in ipairs(context._macros) do + if macro.switch and macro.hotkey == keyDesc then + macro.switch:onClick() + end + end + local hotkey = context._hotkeys[keyDesc] + if hotkey then + if hotkey.single then + if hotkey.callback() then + hotkey.lastExecution = context.now + end + end + if hotkey.switch then + hotkey.switch:setOn(true) + end + end + for i, callback in ipairs(context._callbacks.onKeyDown) do + callback(keyDesc) + end + end, + onKeyUp = function(keyCode, keyboardModifiers) + local keyDesc = determineKeyComboDesc(keyCode, keyboardModifiers) + local hotkey = context._hotkeys[keyDesc] + if hotkey then + if hotkey.switch then + hotkey.switch:setOn(false) + end + end + for i, callback in ipairs(context._callbacks.onKeyUp) do + callback(keyDesc) + end + end, + onKeyPress = function(keyCode, keyboardModifiers, autoRepeatTicks) + local keyDesc = determineKeyComboDesc(keyCode, keyboardModifiers) + local hotkey = context._hotkeys[keyDesc] + if hotkey and not hotkey.single then + if hotkey.callback() then + hotkey.lastExecution = context.now + end + end + for i, callback in ipairs(context._callbacks.onKeyPress) do + callback(keyDesc, autoRepeatTicks) + end + end, + onTalk = function(name, level, mode, text, channelId, pos) + for i, callback in ipairs(context._callbacks.onTalk) do + callback(name, level, mode, text, channelId, pos) + end + end, + onImbuementWindow = function(itemId, slots, activeSlots, imbuements, needItems) + for i, callback in ipairs(context._callbacks.onImbuementWindow) do + callback(itemId, slots, activeSlots, imbuements, needItems) + end + end, + onTextMessage = function(mode, text) + for i, callback in ipairs(context._callbacks.onTextMessage) do + callback(mode, text) + end + end, + onLoginAdvice = function(message) + for i, callback in ipairs(context._callbacks.onLoginAdvice) do + callback(message) + end + end, + onAddThing = function(tile, thing) + for i, callback in ipairs(context._callbacks.onAddThing) do + callback(tile, thing) + end + end, + onRemoveThing = function(tile, thing) + for i, callback in ipairs(context._callbacks.onRemoveThing) do + callback(tile, thing) + end + end, + onCreatureAppear = function(creature) + for i, callback in ipairs(context._callbacks.onCreatureAppear) do + callback(creature) + end + end, + onCreatureDisappear = function(creature) + for i, callback in ipairs(context._callbacks.onCreatureDisappear) do + callback(creature) + end + end, + onCreaturePositionChange = function(creature, newPos, oldPos) + for i, callback in ipairs(context._callbacks.onCreaturePositionChange) do + callback(creature, newPos, oldPos) + end + end, + onCreatureHealthPercentChange = function(creature, healthPercent) + for i, callback in ipairs(context._callbacks.onCreatureHealthPercentChange) do + callback(creature, healthPercent) + end + end, + onUse = function(pos, itemId, stackPos, subType) + for i, callback in ipairs(context._callbacks.onUse) do + callback(pos, itemId, stackPos, subType) + end + end, + onUseWith = function(pos, itemId, target, subType) + for i, callback in ipairs(context._callbacks.onUseWith) do + callback(pos, itemId, target, subType) + end + end, + onContainerOpen = function(container, previousContainer) + for i, callback in ipairs(context._callbacks.onContainerOpen) do + callback(container, previousContainer) + end + end, + onContainerClose = function(container) + for i, callback in ipairs(context._callbacks.onContainerClose) do + callback(container) + end + end, + onContainerUpdateItem = function(container, slot, item, oldItem) + for i, callback in ipairs(context._callbacks.onContainerUpdateItem) do + callback(container, slot, item, oldItem) + end + end, + onMissle = function(missle) + for i, callback in ipairs(context._callbacks.onMissle) do + callback(missle) + end + end, + onAnimatedText = function(thing, text) + for i, callback in ipairs(context._callbacks.onAnimatedText) do + callback(thing, text) + end + end, + onStaticText = function(thing, text) + for i, callback in ipairs(context._callbacks.onStaticText) do + callback(thing, text) + end + end, + onChannelList = function(channels) + for i, callback in ipairs(context._callbacks.onChannelList) do + callback(channels) + end + end, + onOpenChannel = function(channelId, channelName) + for i, callback in ipairs(context._callbacks.onOpenChannel) do + callback(channels) + end + end, + onCloseChannel = function(channelId) + for i, callback in ipairs(context._callbacks.onCloseChannel) do + callback(channelId) + end + end, + onChannelEvent = function(channelId, name, event) + for i, callback in ipairs(context._callbacks.onChannelEvent) do + callback(channelId, name, event) + end + end, + onTurn = function(creature, direction) + for i, callback in ipairs(context._callbacks.onTurn) do + callback(creature, direction) + end + end, + onWalk = function(creature, oldPos, newPos) + for i, callback in ipairs(context._callbacks.onWalk) do + callback(creature, oldPos, newPos) + end + end, + onModalDialog = function(id, title, message, buttons, enterButton, escapeButton, choices, priority) + for i, callback in ipairs(context._callbacks.onModalDialog) do + callback(id, title, message, buttons, enterButton, escapeButton, choices, priority) + end + end, + onGameEditText = function(id, itemId, maxLength, text, writer, time) + for i, callback in ipairs(context._callbacks.onGameEditText) do + callback(id, itemId, maxLength, text, writer, time) + end + end, + onAttackingCreatureChange = function(creature, oldCreature) + for i, callback in ipairs(context._callbacks.onAttackingCreatureChange) do + callback(creature, oldCreature) + end + end, + onManaChange = function(player, mana, maxMana, oldMana, oldMaxMana) + for i, callback in ipairs(context._callbacks.onManaChange) do + callback(player, mana, maxMana, oldMana, oldMaxMana) + end + end, + onAddItem = function(container, slot, item) + for i, callback in ipairs(context._callbacks.onAddItem) do + callback(container, slot, item) + end + end, + onRemoveItem = function(container, slot, item) + for i, callback in ipairs(context._callbacks.onRemoveItem) do + callback(container, slot, item) + end + end, + onStatesChange = function(states, oldStates) + for i, callback in ipairs(context._callbacks.onStatesChange) do + callback(states, oldStates) + end + end, + onGroupSpellCooldown = function(iconId, duration) + for i, callback in ipairs(context._callbacks.onGroupSpellCooldown) do + callback(iconId, duration) + end + end, + onSpellCooldown = function(iconId, duration) + for i, callback in ipairs(context._callbacks.onSpellCooldown) do + callback(iconId, duration) + end + end, + } + } +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/functions/callbacks.lua b/800OTClient/modules/game_bot/functions/callbacks.lua new file mode 100644 index 0000000..55b7a69 --- /dev/null +++ b/800OTClient/modules/game_bot/functions/callbacks.lua @@ -0,0 +1,257 @@ +local context = G.botContext + +-- callback(callbackType, callback) +context.callback = function(callbackType, callback) + if not context._callbacks[callbackType] then + return error("Wrong callback type: " .. callbackType) + end + if callbackType == "onAddThing" or callbackType == "onRemoveThing" then + g_game.enableTileThingLuaCallback(true) + end + + local desc = "lua" + local info = debug.getinfo(2, "Sl") + if info then + desc = info.short_src .. ":" .. info.currentline + end + + local callbackData = {} + table.insert(context._callbacks[callbackType], function(...) + if not callbackData.delay or callbackData.delay < context.now then + local prevExecution = context._currentExecution + context._currentExecution = callbackData + local start = g_clock.realMillis() + callback(...) + local executionTime = g_clock.realMillis() - start + if executionTime > 100 then + context.warning("Slow " .. callbackType .. " (" .. executionTime .. "ms): " .. desc) + end + context._currentExecution = prevExecution + end + end) + local cb = context._callbacks[callbackType] + return { + remove = function() + local index = nil + for i, cb2 in ipairs(context._callbacks[callbackType]) do + if cb == cb2 then + index = i + end + end + if index then + table.remove(context._callbacks[callbackType], index) + end + end + } +end + +-- onKeyDown(callback) -- callback = function(keys) +context.onKeyDown = function(callback) + return context.callback("onKeyDown", callback) +end + +-- onKeyPress(callback) -- callback = function(keys) +context.onKeyPress = function(callback) + return context.callback("onKeyPress", callback) +end + +-- onKeyUp(callback) -- callback = function(keys) +context.onKeyUp = function(callback) + return context.callback("onKeyUp", callback) +end + +-- onTalk(callback) -- callback = function(name, level, mode, text, channelId, pos) +context.onTalk = function(callback) + return context.callback("onTalk", callback) +end + +-- onTextMessage(callback) -- callback = function(mode, text) +context.onTextMessage = function(callback) + return context.callback("onTextMessage", callback) +end + +-- onLoginAdvice(callback) -- callback = function(message) +context.onLoginAdvice = function(callback) + return context.callback("onLoginAdvice", callback) +end + +-- onAddThing(callback) -- callback = function(tile, thing) +context.onAddThing = function(callback) + return context.callback("onAddThing", callback) +end + +-- onRemoveThing(callback) -- callback = function(tile, thing) +context.onRemoveThing = function(callback) + return context.callback("onRemoveThing", callback) +end + +-- onCreatureAppear(callback) -- callback = function(creature) +context.onCreatureAppear = function(callback) + return context.callback("onCreatureAppear", callback) +end + +-- onCreatureDisappear(callback) -- callback = function(creature) +context.onCreatureDisappear = function(callback) + return context.callback("onCreatureDisappear", callback) +end + +-- onCreaturePositionChange(callback) -- callback = function(creature, newPos, oldPos) +context.onCreaturePositionChange = function(callback) + return context.callback("onCreaturePositionChange", callback) +end + +-- onCreatureHealthPercentChange(callback) -- callback = function(creature, healthPercent) +context.onCreatureHealthPercentChange = function(callback) + return context.callback("onCreatureHealthPercentChange", callback) +end + +-- onUse(callback) -- callback = function(pos, itemId, stackPos, subType) +context.onUse = function(callback) + return context.callback("onUse", callback) +end + +-- onUseWith(callback) -- callback = function(pos, itemId, target, subType) +context.onUseWith = function(callback) + return context.callback("onUseWith", callback) +end + +-- onContainerOpen -- callback = function(container, previousContainer) +context.onContainerOpen = function(callback) + return context.callback("onContainerOpen", callback) +end + +-- onContainerClose -- callback = function(container) +context.onContainerClose = function(callback) + return context.callback("onContainerClose", callback) +end + +-- onContainerUpdateItem -- callback = function(container, slot, item, oldItem) +context.onContainerUpdateItem = function(callback) + return context.callback("onContainerUpdateItem", callback) +end + +-- onMissle -- callback = function(missle) +context.onMissle = function(callback) + return context.callback("onMissle", callback) +end + +-- onAnimatedText -- callback = function(thing, text) +context.onAnimatedText = function(callback) + return context.callback("onAnimatedText", callback) +end + +-- onStaticText -- callback = function(thing, text) +context.onStaticText = function(callback) + return context.callback("onStaticText", callback) +end + +-- onChannelList -- callback = function(channels) +context.onChannelList = function(callback) + return context.callback("onChannelList", callback) +end + +-- onOpenChannel -- callback = function(channelId, name) +context.onOpenChannel = function(callback) + return context.callback("onOpenChannel", callback) +end + +-- onCloseChannel -- callback = function(channelId) +context.onCloseChannel = function(callback) + return context.callback("onCloseChannel", callback) +end + +-- onChannelEvent -- callback = function(channelId, name, event) +context.onChannelEvent = function(callback) + return context.callback("onChannelEvent", callback) +end + +-- onTurn -- callback = function(creature, direction) +context.onTurn = function(callback) + return context.callback("onTurn", callback) +end + +-- onWalk -- callback = function(creature, oldPos, newPos) +context.onWalk = function(callback) + return context.callback("onWalk", callback) +end + +-- onImbuementWindow -- callback = function(itemId, slots, activeSlots, imbuements, needItems) +context.onImbuementWindow = function(callback) + return context.callback("onImbuementWindow", callback) +end + +-- onModalDialog -- callback = function(id, title, message, buttons, enterButton, escapeButton, choices, priority) -- priority is unused, ignore it +context.onModalDialog = function(callback) + return context.callback("onModalDialog", callback) +end + +-- onAttackingCreatureChange -- callback = function(creature, oldCreature) +context.onAttackingCreatureChange = function(callback) + return context.callback("onAttackingCreatureChange", callback) +end + +-- onManaChange -- callback = function(player, mana, maxMana, oldMana, oldMaxMana) +context.onManaChange = function(callback) + return context.callback("onManaChange", callback) +end + +-- onAddItem - callback = function(container, slot, item, oldItem) +context.onAddItem = function(callback) + return context.callback("onAddItem", callback) +end + +-- onRemoveItem - callback = function(container, slot, item) +context.onRemoveItem = function(callback) + return context.callback("onRemoveItem", callback) +end + +-- onStatesChange - callback = function(states, oldStates) +context.onStatesChange = function(callback) + return context.callback("onStatesChange", callback) +end + +-- onGameEditText - callback = function(id, itemId, maxLength, text, writer, time) +context.onGameEditText = function(callback) + return context.callback("onGameEditText", callback) +end + +-- onSpellCooldown - callback = function(iconId, duration) +context.onSpellCooldown = function(callback) + return context.callback("onSpellCooldown", callback) +end + +-- onGroupSpellCooldown - callback = function(iconId, duration) +context.onGroupSpellCooldown = function(callback) + return context.callback("onGroupSpellCooldown", callback) +end + +-- CUSTOM CALLBACKS + +-- listen(name, callback) -- callback = function(text, channelId, pos) +context.listen = function(name, callback) + if not name then return context.error("listen: invalid name") end + name = name:lower() + return context.onTalk(function(name2, level, mode, text, channelId, pos) + if name == name2:lower() then + callback(text, channelId, pos) + end + end) +end + +-- onPlayerPositionChange(callback) -- callback = function(newPos, oldPos) +context.onPlayerPositionChange = function(callback) + return context.onCreaturePositionChange(function(creature, newPos, oldPos) + if creature == context.player then + callback(newPos, oldPos) + end + end) +end + +-- onPlayerHealthChange(callback) -- callback = function(healthPercent) +context.onPlayerHealthChange = function(callback) + return context.onCreatureHealthPercentChange(function(creature, healthPercent) + if creature == context.player then + callback(healthPercent) + end + end) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/functions/config.lua b/800OTClient/modules/game_bot/functions/config.lua new file mode 100644 index 0000000..a46dbda --- /dev/null +++ b/800OTClient/modules/game_bot/functions/config.lua @@ -0,0 +1,266 @@ +--[[ +Config - create, load and save config file (.json / .cfg) +Used by cavebot and other things +]]-- + +local context = G.botContext +context.Config = {} +local Config = context.Config + +Config.exist = function(dir) + return g_resources.directoryExists(context.configDir .. "/" .. dir) +end + +Config.create = function(dir) + g_resources.makeDir(context.configDir .. "/" .. dir) + return Config.exist(dir) +end + +Config.list = function(dir) + if not Config.exist(dir) then + if not Config.create(dir) then + return contex.error("Can't create config dir: " .. context.configDir .. "/" .. dir) + end + end + local list = g_resources.listDirectoryFiles(context.configDir .. "/" .. dir) + local correctList = {} + for k,v in ipairs(list) do -- filter files + local nv = v:gsub(".json", ""):gsub(".cfg", "") + if nv ~= v then + table.insert(correctList, nv) + end + end + return correctList +end + +-- load config from string insteaf of file +Config.parse = function(data) + local status, result = pcall(function() + if data:len() < 2 then return {} end + return json.decode(data) + end) + if status and type(result) == 'table' then + return result + end + local status, result = pcall(function() + return table.decodeStringPairList(data) + end) + if status and type(result) == 'table' then + return result + end + return context.error("Invalid config format") +end + +Config.load = function(dir, name) + local file = context.configDir .. "/" .. dir .. "/" .. name .. ".json" + if g_resources.fileExists(file) then -- load json + local status, result = pcall(function() + local data = g_resources.readFileContents(file) + if data:len() < 2 then return {} end + return json.decode(data) + end) + if not status then + context.error("Invalid json config (" .. name .. "): " .. result) + return {} + end + return result + end + file = context.configDir .. "/" .. dir .. "/" .. name .. ".cfg" + if g_resources.fileExists(file) then -- load cfg + local status, result = pcall(function() + return table.decodeStringPairList(g_resources.readFileContents(file)) + end) + if not status then + context.error("Invalid cfg config (" .. name .. "): " .. result) + return {} + end + return result + end + return context.error("Config " .. file .. " doesn't exist") +end + +Config.loadRaw = function(dir, name) + local file = context.configDir .. "/" .. dir .. "/" .. name .. ".json" + if g_resources.fileExists(file) then -- load json + return g_resources.readFileContents(file) + end + file = context.configDir .. "/" .. dir .. "/" .. name .. ".cfg" + if g_resources.fileExists(file) then -- load cfg + return g_resources.readFileContents(file) + end + return context.error("Config " .. file .. " doesn't exist") +end + +Config.save = function(dir, name, value, forcedExtension) + if not Config.exist(dir) then + if not Config.create(dir) then + return contex.error("Can't create config dir: " .. context.configDir .. "/" .. dir) + end + end + if type(value) ~= 'table' then + return context.error("Invalid config value type: " .. type(value) .. ", should be table") + end + local file = context.configDir .. "/" .. dir .. "/" .. name + if (table.isStringPairList(value) and forcedExtension ~= "json") or forcedExtension == "cfg" then -- cfg + g_resources.writeFileContents(file .. ".cfg", table.encodeStringPairList(value)) + else + g_resources.writeFileContents(file .. ".json", json.encode(value, 2)) + end + return true +end + +Config.remove = function(dir, name) + local file = context.configDir .. "/" .. dir .. "/" .. name .. ".json" + local ret = false + if g_resources.fileExists(file) then + g_resources.deleteFile(file) + ret = true + end + file = context.configDir .. "/" .. dir .. "/" .. name .. ".cfg" + if g_resources.fileExists(file) then + g_resources.deleteFile(file) + ret = true + end + return ret +end + +-- setup is used for BotConfig widget +-- not done yet +Config.setup = function(dir, widget, configExtension, callback) + if type(dir) ~= 'string' or dir:len() == 0 then + return context.error("Invalid config dir") + end + if not Config.exist(dir) and not Config.create(dir) then + return context.error("Can't create config dir: " .. dir) + end + if type(context.storage._configs) ~= "table" then + context.storage._configs = {} + end + if type(context.storage._configs[dir]) ~= "table" then + context.storage._configs[dir] = { + enabled = false, + selected = "" + } + else + widget.switch:setOn(context.storage._configs[dir].enabled) + end + + local isRefreshing = false + local refresh = function() + isRefreshing = true + local configs = Config.list(dir) + local configIndex = 1 + widget.list:clear() + for v,k in ipairs(configs) do + widget.list:addOption(k) + if k == context.storage._configs[dir].selected then + configIndex = v + end + end + local data = nil + if #configs > 0 then + widget.list:setCurrentIndex(configIndex) + context.storage._configs[dir].selected = widget.list:getCurrentOption().text + data = Config.load(dir, configs[configIndex]) + else + context.storage._configs[dir].selected = nil + end + context.storage._configs[dir].enabled = widget.switch:isOn() + isRefreshing = false + callback(context.storage._configs[dir].selected, widget.switch:isOn(), data) + end + + widget.list.onOptionChange = function(widget) + if not isRefreshing then + context.storage._configs[dir].selected = widget:getCurrentOption().text + refresh() + end + end + + widget.switch.onClick = function() + widget.switch:setOn(not widget.switch:isOn()) + refresh() + end + + widget.add.onClick = function() + context.UI.SinglelineEditorWindow("config_name", {title="Enter config name"}, function(name) + name = name:gsub("%s+", "_") + if name:len() == 0 or name:len() >= 30 or name:find("/") or name:find("\\") then + return context.error("Invalid config name") + end + local file = context.configDir .. "/" .. dir .. "/" .. name .. "." .. configExtension + if g_resources.fileExists(file) then + return context.error("Config " .. name .. " already exist") + end + if configExtension == "json" then + g_resources.writeFileContents(file, json.encode({})) + else + g_resources.writeFileContents(file, "") + end + context.storage._configs[dir].selected = name + widget.switch:setOn(false) + refresh() + end) + end + + widget.edit.onClick = function() + local name = context.storage._configs[dir].selected + if not name then return end + context.UI.MultilineEditorWindow(Config.loadRaw(dir, name), {title="Config editor - " .. name .. " in " .. dir}, function(newValue) + local data = Config.parse(newValue) + Config.save(dir, name, data, configExtension) + refresh() + end) + end + + widget.remove.onClick = function() + local name = context.storage._configs[dir].selected + if not name then return end + context.UI.ConfirmationWindow("Config removal", "Do you want to remove config " .. name .. " from " .. dir .. "?", function() + Config.remove(dir, name) + widget.switch:setOn(false) + refresh() + end) + end + + refresh() + + return { + isOn = function() + return widget.switch:isOn() + end, + isOff = function() + return not widget.switch:isOn() + end, + setOn = function(val) + if val == false then + if widget.switch:isOn() then + widget.switch:onClick() + end + return + end + if not widget.switch:isOn() then + widget.switch:onClick() + end + end, + setOff = function(val) + if val == false then + if not widget.switch:isOn() then + widget.switch:onClick() + end + return + end + if widget.switch:isOn() then + widget.switch:onClick() + end + end, + save = function(data) + Config.save(dir, context.storage._configs[dir].selected, data, configExtension) + end, + refresh = refresh, + reload = refresh, + getActiveConfigName = function() + return context.storage._configs[dir].selected + end + } +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/functions/const.lua b/800OTClient/modules/game_bot/functions/const.lua new file mode 100644 index 0000000..c43757d --- /dev/null +++ b/800OTClient/modules/game_bot/functions/const.lua @@ -0,0 +1,25 @@ +local context = G.botContext + +context.North = 0 +context.East = 1 +context.South = 2 +context.West = 3 +context.NorthEast = 4 +context.SouthEast = 5 +context.SouthWest = 6 +context.NorthWest = 7 + +context.InventorySlotOther = 0 +context.InventorySlotHead = 1 +context.InventorySlotNeck = 2 +context.InventorySlotBack = 3 +context.InventorySlotBody = 4 +context.InventorySlotRight = 5 +context.InventorySlotLeft = 6 +context.InventorySlotLeg = 7 +context.InventorySlotFeet = 8 +context.InventorySlotFinger = 9 +context.InventorySlotAmmo = 10 +context.InventorySlotPurse = 11 +context.InventorySlotFirst = 1 +context.InventorySlotLast = 10 diff --git a/800OTClient/modules/game_bot/functions/icon.lua b/800OTClient/modules/game_bot/functions/icon.lua new file mode 100644 index 0000000..dcd48a2 --- /dev/null +++ b/800OTClient/modules/game_bot/functions/icon.lua @@ -0,0 +1,176 @@ +local context = G.botContext + +local iconsWithoutPosition = 0 + +context.addIcon = function(id, options, callback) +--[[ + Available options: + item: {id=2160, count=100} + outfit: outfit table ({}) + text: string + x: float (0.0 - 1.0) + y: float (0.0 - 1.0) + hotkey: string + switchable: true / false [default: true] + movable: true / false [default: true] + phantom: true / false [defaule: false] +]]-- + local panel = modules.game_interface.gameMapPanel + if type(id) ~= "string" or id:len() < 1 then + return context.error("Invalid id for addIcon") + end + if options.switchable == false and type(callback) ~= 'function' then + return context.error("Invalid callback for addIcon") + end + if type(context.storage._icons) ~= "table" then + context.storage._icons = {} + end + if type(context.storage._icons[id]) ~= "table" then + context.storage._icons[id] = {} + end + local config = context.storage._icons[id] + local widget = g_ui.createWidget("BotIcon", panel) + widget.botWidget = true + widget.botIcon = true + + if type(config.x) ~= 'number' and type(config.y) ~= 'number' then + if type(options.x) == 'number' and type(options.y) == 'number' then + config.x = math.min(1.0, math.max(0.0, options.x)) + config.y = math.min(1.0, math.max(0.0, options.y)) + else + config.x = 0.01 + math.floor(iconsWithoutPosition / 5) / 10 + config.y = 0.05 + (iconsWithoutPosition % 5) / 5 + iconsWithoutPosition = iconsWithoutPosition + 1 + end + end + + if options.item then + if type(options.item) == 'number' then + widget.item:setItemId(options.item) + else + widget.item:setItemId(options.item.id) + widget.item:setItemCount(options.item.count or 1) + widget.item:setShowCount(false) + end + end + + if options.outfit then + widget.creature:setOutfit(options.outfit) + end + + if options.switchable == false then + widget.status:hide() + widget.status:setOn(true) + else + if config.enabled ~= true then + config.enabled = false + end + widget.status:setOn(config.enabled) + end + + if options.text then + if options.switchable ~= false then + widget.status:hide() + if widget.status:isOn() then + widget.text:setColor('green') + else + widget.text:setColor('red') + end + end + widget.text:setText(options.text) + end + + widget.setOn = function(val) + widget.status:setOn(val) + if widget.status:isOn() then + widget.text:setColor('green') + else + widget.text:setColor('red') + end + config.enabled = widget.status:isOn() + end + + widget.onClick = function(widget) + if options.switchable ~= false then + widget.setOn(not widget.status:isOn()) + if type(callback) == 'table' then + callback.setOn(config.enabled) + return + end + end + + callback(widget, widget.status:isOn()) + end + + if options.hotkey then + widget.hotkey:setText(options.hotkey) + context.hotkey(options.hotkey, "", function() + widget:onClick() + end, nil, options.switchable ~= false) + else + widget.hotkey:hide() + end + + if options.movable ~= false then + widget.onDragEnter = function(widget, mousePos) + if not g_keyboard.isCtrlPressed() then + return false + end + widget:breakAnchors() + widget.movingReference = { x = mousePos.x - widget:getX(), y = mousePos.y - widget:getY() } + return true + end + + widget.onDragMove = function(widget, mousePos, moved) + local parentRect = widget:getParent():getRect() + local x = math.min(math.max(parentRect.x, mousePos.x - widget.movingReference.x), parentRect.x + parentRect.width - widget:getWidth()) + local y = math.min(math.max(parentRect.y - widget:getParent():getMarginTop(), mousePos.y - widget.movingReference.y), parentRect.y + parentRect.height - widget:getHeight()) + widget:move(x, y) + return true + end + + widget.onDragLeave = function(widget, pos) + local parent = widget:getParent() + local parentRect = parent:getRect() + local x = widget:getX() - parentRect.x + local y = widget:getY() - parentRect.y + local width = parentRect.width - widget:getWidth() + local height = parentRect.height - widget:getHeight() + + config.x = math.min(1, math.max(0, x / width)) + config.y = math.min(1, math.max(0, y / height)) + + widget:addAnchor(AnchorHorizontalCenter, 'parent', AnchorHorizontalCenter) + widget:addAnchor(AnchorVerticalCenter, 'parent', AnchorVerticalCenter) + widget:setMarginTop(math.max(height * (-0.5) - parent:getMarginTop(), height * (-0.5 + config.y))) + widget:setMarginLeft(width * (-0.5 + config.x)) + return true + end + end + + widget.onGeometryChange = function(widget) + if widget:isDragging() then return end + local parent = widget:getParent() + local parentRect = parent:getRect() + local width = parentRect.width - widget:getWidth() + local height = parentRect.height - widget:getHeight() + widget:setMarginTop(math.max(height * (-0.5) - parent:getMarginTop(), height * (-0.5 + config.y))) + widget:setMarginLeft(width * (-0.5 + config.x)) + end + + if options.phantom ~= true then + widget.onMouseRelease = function() + return true + end + end + + if options.switchable ~= false then + if type(callback) == 'table' then + callback.setOn(config.enabled) + callback.icon = widget + else + callback(widget, widget.status:isOn()) + end + end + return widget +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/functions/main.lua b/800OTClient/modules/game_bot/functions/main.lua new file mode 100644 index 0000000..211ee0c --- /dev/null +++ b/800OTClient/modules/game_bot/functions/main.lua @@ -0,0 +1,211 @@ +local context = G.botContext + +-- MAIN BOT FUNCTION +-- macro(timeout, callback) +-- macro(timeout, name, callback) +-- macro(timeout, name, callback, parent) +-- macro(timeout, name, hotkey, callback) +-- macro(timeout, name, hotkey, callback, parent) +context.macro = function(timeout, name, hotkey, callback, parent) + if type(timeout) ~= 'number' or timeout < 1 then + error("Invalid timeout for macro: " .. tostring(timeout)) + end + if type(name) == 'function' then + callback = name + name = "" + hotkey = "" + elseif type(hotkey) == 'function' then + parent = callback + callback = hotkey + hotkey = "" + elseif type(callback) ~= 'function' then + error("Invalid callback for macro: " .. tostring(callback)) + end + if hotkey == nil then + hotkey = "" + end + if type(name) ~= 'string' or type(hotkey) ~= 'string' then + error("Invalid name or hotkey for macro") + end + if not parent then + parent = context.panel + end + if hotkey:len() > 0 then + hotkey = retranslateKeyComboDesc(hotkey) + end + + -- min timeout is 50, to avoid lags + if timeout < 50 then + timeout = 50 + end + + table.insert(context._macros, { + enabled = false, + name = name, + timeout = timeout, + lastExecution = context.now + math.random(0, 100), + hotkey = hotkey, + }) + local macro = context._macros[#context._macros] + + macro.isOn = function() + return macro.enabled + end + macro.isOff = function() + return not macro.enabled + end + macro.toggle = function(widget) + if macro.isOn() then + macro.setOff() + else + macro.setOn() + end + end + macro.setOn = function(val) + if val == false then + return macro.setOff() + end + macro.enabled = true + context.storage._macros[name] = true + if macro.switch then + macro.switch:setOn(true) + end + if macro.icon then + macro.icon.setOn(true) + end + end + macro.setOff = function(val) + if val == false then + return macro.setOn() + end + macro.enabled = false + context.storage._macros[name] = false + if macro.switch then + macro.switch:setOn(false) + end + if macro.icon then + macro.icon.setOn(false) + end + end + + if name:len() > 0 then + -- creature switch + local text = name + if hotkey:len() > 0 then + text = name .. " [" .. hotkey .. "]" + end + macro.switch = context.addSwitch("macro_" .. (#context._macros + 1), text, macro.toggle, parent) + + -- load state + if context.storage._macros[name] == true then + macro.setOn() + end + else + macro.enabled = true -- unnamed macros are enabled by default + end + + local desc = "lua" + local info = debug.getinfo(2, "Sl") + if info then + desc = info.short_src .. ":" .. info.currentline + end + + macro.callback = function(macro) + if not macro.delay or macro.delay < context.now then + context._currentExecution = macro + local start = g_clock.realMillis() + callback(macro) + local executionTime = g_clock.realMillis() - start + if executionTime > 100 then + context.warning("Slow macro (" .. executionTime .. "ms): " .. macro.name .. " - " .. desc) + end + context._currentExecution = nil + return true + end + end + return macro +end + +-- hotkey(keys, callback) +-- hotkey(keys, name, callback) +-- hotkey(keys, name, callback, parent) +context.hotkey = function(keys, name, callback, parent, single) + if type(name) == 'function' then + callback = name + name = "" + end + if not parent then + parent = context.panel + end + keys = retranslateKeyComboDesc(keys) + if not keys or #keys == 0 then + return context.error("Invalid hotkey keys " .. tostring(name)) + end + if context._hotkeys[keys] then + return context.error("Duplicated hotkey: " .. keys) + end + + local switch = nil + if name:len() > 0 then + switch = context._addHotkeySwitch(name, keys, parent) + end + + context._hotkeys[keys] = { + name = name, + lastExecution = context.now, + switch = switch, + single = single + } + + local desc = "lua" + local info = debug.getinfo(2, "Sl") + if info then + desc = info.short_src .. ":" .. info.currentline + end + + local hotkeyData = context._hotkeys[keys] + hotkeyData.callback = function() + if not hotkeyData.delay or hotkeyData.delay < context.now then + context._currentExecution = hotkeyData + local start = g_clock.realMillis() + callback() + local executionTime = g_clock.realMillis() - start + if executionTime > 100 then + context.warning("Slow hotkey (" .. executionTime .. "ms): " .. hotkeyData.name .. " - " .. desc) + end + context._currentExecution = nil + return true + end + end + + return hotkeyData +end + +-- singlehotkey(keys, callback) +-- singlehotkey(keys, name, callback) +-- singlehotkey(keys, name, callback, parent) +context.singlehotkey = function(keys, name, callback, parent) + if type(name) == 'function' then + callback = name + name = "" + end + return context.hotkey(keys, name, callback, parent, true) +end + +-- schedule(timeout, callback) +context.schedule = function(timeout, callback) + local extecute_time = g_clock.millis() + timeout + table.insert(context._scheduler, { + execution = extecute_time, + callback = callback + }) + table.sort(context._scheduler, function(a, b) return a.execution < b.execution end) +end + +-- delay(duration) -- block execution of current macro/hotkey/callback for x milliseconds +context.delay = function(duration) + if not context._currentExecution then + return context.error("Invalid usage of delay function, it should be used inside callbacks") + end + context._currentExecution.delay = context.now + duration +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/functions/map.lua b/800OTClient/modules/game_bot/functions/map.lua new file mode 100644 index 0000000..1e27c92 --- /dev/null +++ b/800OTClient/modules/game_bot/functions/map.lua @@ -0,0 +1,267 @@ +local context = G.botContext + +context.getMapView = function() return modules.game_interface.getMapPanel() end +context.getMapPanel = context.getMapView +context.zoomIn = function() modules.game_interface.getMapPanel():zoomIn() end +context.zoomOut = function() modules.game_interface.getMapPanel():zoomOut() end + +context.getSpectators = function(param1, param2) +--[[ + if param1 is table (position) then it's used for central position, then param2 is used as param1 + if param1 is creature, then creature position and direction of creature is used, then param2 is used as param1 + if param1 is true/false then it's used for multifloor, example: getSpectators(true) + if param1 is string then it's used for getSpectatorsByPattern +]]-- + local pos = context.player:getPosition() + local direction = context.player:getDirection() + if type(param1) == 'table' then + pos = param1 + direction = 8 -- invalid direction + param1 = param2 + end + if type(param1) == 'userdata' then + pos = param1:getPosition() + direction = param1:getDirection() + param1 = param2 + end + + if type(param1) == 'string' then + return g_map.getSpectatorsByPattern(pos, param1, direction) + end + + local multifloor = false + if type(param1) == 'boolean' and param1 == true then + multifloor = true + end + return g_map.getSpectators(pos, multifloor) +end + +context.getCreatureById = function(id, multifloor) + if type(id) ~= 'number' then return nil end + if multifloor ~= true then + multifloor = false + end + for i, spec in ipairs(g_map.getSpectators(context.player:getPosition(), multifloor)) do + if spec:getId() == id then + return spec + end + end + return nil +end + +context.getCreatureByName = function(name, multifloor) + if not name then return nil end + name = name:lower() + if multifloor ~= true then + multifloor = false + end + for i, spec in ipairs(g_map.getSpectators(context.player:getPosition(), multifloor)) do + if spec:getName():lower() == name then + return spec + end + end + return nil +end + +context.getPlayerByName = function(name, multifloor) + if not name then return nil end + name = name:lower() + if multifloor ~= true then + multifloor = false + end + for i, spec in ipairs(g_map.getSpectators(context.player:getPosition(), multifloor)) do + if spec:isPlayer() and spec:getName():lower() == name then + return spec + end + end + return nil +end + +context.findAllPaths = function(start, maxDist, params) + --[[ + Available params: + ignoreLastCreature + ignoreCreatures + ignoreNonPathable + ignoreNonWalkable + ignoreStairs + ignoreCost + allowUnseen + allowOnlyVisibleTiles + maxDistanceFrom + ]]-- + if type(params) ~= 'table' then + params = {} + end + for key, value in pairs(params) do + if value == nil or value == false then + params[key] = 0 + elseif value == true then + params[key] = 1 + end + end + if type(params['maxDistanceFrom']) == 'table' then + if #params['maxDistanceFrom'] == 2 then + params['maxDistanceFrom'] = params['maxDistanceFrom'][1].x .. "," .. params['maxDistanceFrom'][1].y .. + "," .. params['maxDistanceFrom'][1].z .. "," .. params['maxDistanceFrom'][2] + elseif #params['maxDistanceFrom'] == 4 then + params['maxDistanceFrom'] = params['maxDistanceFrom'][1] .. "," .. params['maxDistanceFrom'][2] .. + "," .. params['maxDistanceFrom'][3] .. "," .. params['maxDistanceFrom'][4] + end + end + return g_map.findEveryPath(start, maxDist, params) +end +context.findEveryPath = context.findAllPaths + +context.translateAllPathsToPath = function(paths, destPos) + local predirections = {} + local directions = {} + local destPosStr = destPos + if type(destPos) ~= 'string' then + destPosStr = destPos.x .. "," .. destPos.y .. "," .. destPos.z + end + + while destPosStr:len() > 0 do + local node = paths[destPosStr] + if not node then + break + end + if node[3] < 0 then + break + end + table.insert(predirections, node[3]) + destPosStr = node[4] + end + -- reverse + for i=#predirections,1,-1 do + table.insert(directions, predirections[i]) + end + return directions +end +context.translateEveryPathToPath = context.translateAllPathsToPath + + +context.findPath = function(startPos, destPos, maxDist, params) + --[[ + Available params: + ignoreLastCreature + ignoreCreatures + ignoreNonPathable + ignoreNonWalkable + ignoreStairs + ignoreCost + allowUnseen + allowOnlyVisibleTiles + precision + marginMin + marginMax + maxDistanceFrom + ]]-- + if not destPos or startPos.z ~= destPos.z then + return + end + if type(maxDist) ~= 'number' then + maxDist = 100 + end + if type(params) ~= 'table' then + params = {} + end + local destPosStr = destPos.x .. "," .. destPos.y .. "," .. destPos.z + params["destination"] = destPosStr + local paths = context.findAllPaths(startPos, maxDist, params) + local marginMin = params.marginMin or params.minMargin + local marginMax = params.marginMax or params.maxMargin + if type(marginMin) == 'number' and type(marginMax) == 'number' then + local bestCandidate = nil + local bestCandidatePos = nil + for x = -marginMax, marginMax do + for y = -marginMax, marginMax do + if math.abs(x) >= marginMin or math.abs(y) >= marginMin then + local dest = (destPos.x + x) .. "," .. (destPos.y + y) .. "," .. destPos.z + local node = paths[dest] + if node and (not bestCandidate or bestCandidate[1] > node[1]) then + bestCandidate = node + bestCandidatePos = dest + end + end + end + end + if bestCandidate then + return context.translateAllPathsToPath(paths, bestCandidatePos) + end + return + end + + if not paths[destPosStr] then + local precision = params.precision + if type(precision) == 'number' then + for p = 1, precision do + local bestCandidate = nil + local bestCandidatePos = nil + for x = -p, p do + for y = -p, p do + local dest = (destPos.x + x) .. "," .. (destPos.y + y) .. "," .. destPos.z + local node = paths[dest] + if node and (not bestCandidate or bestCandidate[1] > node[1]) then + bestCandidate = node + bestCandidatePos = dest + end + end + end + if bestCandidate then + return context.translateAllPathsToPath(paths, bestCandidatePos) + end + end + end + return nil + end + + return context.translateAllPathsToPath(paths, destPos) +end +context.getPath = context.findPath + +-- also works as autoWalk(dirs) where dirs is a list eg.: {1,2,3,0,1,1,2,} +context.autoWalk = function(destination, maxDist, params) + if type(destination) == "table" and table.isList(destination) and not maxDist and not params then + g_game.autoWalk(destination, {x=0,y=0,z=0}) + return true + end + + -- Available params same as for findPath + local path = context.findPath(context.player:getPosition(), destination, maxDist, params) + if not path then + return false + end + -- autowalk without prewalk animation + g_game.autoWalk(path, {x=0,y=0,z=0}) + return true +end + +context.getTileUnderCursor = function() + if not modules.game_interface.gameMapPanel.mousePos then return end + return modules.game_interface.gameMapPanel:getTile(modules.game_interface.gameMapPanel.mousePos) +end + +context.canShoot = function(pos, distance) + if not distance then distance = 5 end + local tile = g_map.getTile(pos, distance) + if tile then + return tile:canShoot(distance) + end + return false +end + +context.isTrapped = function(creature) + if not creature then + creature = context.player + end + local pos = creature:getPosition() + local dirs = {{-1,1}, {0,1}, {1,1}, {-1, 0}, {1, 0}, {-1, -1}, {0, -1}, {1, -1}} + for i=1,#dirs do + local tile = g_map.getTile({x=pos.x-dirs[i][1],y=pos.y-dirs[i][2],z=pos.z}) + if tile and tile:isWalkable(false) then + return false + end + end + return true +end diff --git a/800OTClient/modules/game_bot/functions/npc.lua b/800OTClient/modules/game_bot/functions/npc.lua new file mode 100644 index 0000000..b650f4c --- /dev/null +++ b/800OTClient/modules/game_bot/functions/npc.lua @@ -0,0 +1,130 @@ +local context = G.botContext + +context.NPC = {} + +context.NPC.talk = function(text) + if g_game.getClientVersion() >= 810 then + g_game.talkChannel(11, 0, text) + else + return context.say(text) + end +end +context.NPC.say = context.NPC.talk + +context.NPC.isTrading = function() + return modules.game_npctrade.npcWindow and modules.game_npctrade.npcWindow:isVisible() +end +context.NPC.hasTrade = context.NPC.isTrading +context.NPC.hasTradeWindow = context.NPC.isTrading +context.NPC.isTradeOpen = context.NPC.isTrading + +context.NPC.getSellItems = function() + if not context.NPC.isTrading() then return {} end + local items = {} + for i, item in ipairs(modules.game_npctrade.tradeItems[modules.game_npctrade.SELL]) do + table.insert(items, { + item = item.ptr, + id = item.ptr:getId(), + count = item.ptr:getCount(), + name = item.name, + subType = item.ptr:getSubType(), + weight = item.weight / 100, + price = item.price + }) + end + return items +end + +context.NPC.getBuyItems = function() + if not context.NPC.isTrading() then return {} end + local items = {} + for i, item in ipairs(modules.game_npctrade.tradeItems[modules.game_npctrade.BUY]) do + table.insert(items, { + item = item.ptr, + id = item.ptr:getId(), + count = item.ptr:getCount(), + name = item.name, + subType = item.ptr:getSubType(), + weight = item.weight / 100, + price = item.price + }) + end + return items +end + +context.NPC.getSellQuantity = function(item) + if not context.NPC.isTrading() then return 0 end + if type(item) == 'number' then + item = Item.create(item) + end + return modules.game_npctrade.getSellQuantity(item) +end + +context.NPC.canTradeItem = function(item) + if not context.NPC.isTrading() then return false end + if type(item) == 'number' then + item = Item.create(item) + end + return modules.game_npctrade.canTradeItem(item) +end + +context.NPC.sell = function(item, count, ignoreEquipped) + if type(item) == 'number' then + for i, entry in ipairs(context.NPC.getSellItems()) do + if entry.id == item then + item = entry.item + break + end + end + if type(item) == 'number' then + item = Item.create(item) + end + end + if count == 0 then + count = 1 + end + if count == nil or count == -1 then + count = context.NPC.getSellQuantity(item) + end + if ignoreEquipped == nil then + ignoreEquipped = true + end + g_game.sellItem(item, count, ignoreEquipped) +end + +context.NPC.buy = function(item, count, ignoreCapacity, withBackpack) + if type(item) == 'number' then + for i, entry in ipairs(context.NPC.getBuyItems()) do + if entry.id == item then + item = entry.item + break + end + end + if type(item) == 'number' then + item = Item.create(item) + end + end + if count == nil or count <= 0 then + count = 1 + end + if ignoreCapacity == nil then + ignoreCapacity = false + end + if withBackpack == nil then + withBackpack = false + end + g_game.buyItem(item, count, ignoreCapacity, withBackpack) +end + +context.NPC.sellAll = function() + if not context.NPC.isTrading() then return false end + modules.game_npctrade.sellAll() +end + +context.NPC.closeTrade = function() + modules.game_npctrade.closeNpcTrade() +end +context.NPC.close = context.NPC.closeTrade +context.NPC.finish = context.NPC.closeTrade +context.NPC.endTrade = context.NPC.closeTrade +context.NPC.finishTrade = context.NPC.closeTrade \ No newline at end of file diff --git a/800OTClient/modules/game_bot/functions/player.lua b/800OTClient/modules/game_bot/functions/player.lua new file mode 100644 index 0000000..baea6dc --- /dev/null +++ b/800OTClient/modules/game_bot/functions/player.lua @@ -0,0 +1,167 @@ +local context = G.botContext + +context.name = function() return context.player:getName() end + +context.hp = function() return context.player:getHealth() end +context.mana = function() return context.player:getMana() end +context.hppercent = function() return context.player:getHealthPercent() end +context.manapercent = function() if context.player:getMaxMana() <= 1 then return 100 else return math.floor(context.player:getMana() * 100 / context.player:getMaxMana()) end end +context.maxhp = function() return context.player:getMaxHealth() end +context.maxmana = function() return context.player:getMaxMana() end +context.hpmax = function() return context.player:getMaxHealth() end +context.manamax = function() return context.player:getMaxMana() end + +context.cap = function() return context.player:getCapacity() end +context.freecap = function() return context.player:getFreeCapacity() end +context.maxcap = function() return context.player:getTotalCapacity() end +context.capmax = function() return context.player:getTotalCapacity() end + +context.exp = function() return context.player:getExperience() end +context.lvl = function() return context.player:getLevel() end +context.level = function() return context.player:getLevel() end + +context.mlev = function() return context.player:getMagicLevel() end +context.magic = function() return context.player:getMagicLevel() end +context.mlevel = function() return context.player:getMagicLevel() end + +context.soul = function() return context.player:getSoul() end +context.stamina = function() return context.player:getStamina() end +context.voc = function() return context.player:getVocation() end +context.vocation = function() return context.player:getVocation() end + +context.bless = function() return context.player:getBlessings() end +context.blesses = function() return context.player:getBlessings() end +context.blessings = function() return context.player:getBlessings() end + + +context.pos = function() return context.player:getPosition() end +context.posx = function() return context.player:getPosition().x end +context.posy = function() return context.player:getPosition().y end +context.posz = function() return context.player:getPosition().z end + +context.direction = function() return context.player:getDirection() end +context.speed = function() return context.player:getSpeed() end +context.skull = function() return context.player:getSkull() end +context.outfit = function() return context.player:getOutfit() end + +context.setOutfit = function(outfit) + modules.game_outfit.ignoreNextOutfitWindow = g_clock.millis() + g_game.requestOutfit() + context.schedule(100, function() + g_game.changeOutfit(outfit) + end) +end +context.changeOutfit = context.setOutfit +context.setSpeed = function(value) context.player:setSpeed(value) end + +context.walk = function(dir) return modules.game_walking.walk(dir) end +context.turn = function(dir) return g_game.turn(dir) end + +-- game releated +context.getChannels = function() + -- return { channelId = channelName } + return modules.game_console.channels +end +context.getChannelId = function(name) + for id, channel in pairs(context.getChannels()) do + if name:lower() == channel:lower() then + return id + end + end + return nil +end +context.getChannel = context.getChannelId + +context.say = g_game.talk +context.talk = g_game.talk +context.yell = function(text) g_game.talkChannel(3, 0, text) end +context.talkChannel = function(channel, text) g_game.talkChannel(7, channel, text) end +context.sayChannel = context.talkChannel +context.talkPrivate = function(receiver, text) g_game.talkPrivate(5, receiver, text) end +context.sayPrivate = context.talkPrivate + +context.talkNpc = function(text) + if g_game.getClientVersion() >= 810 then + g_game.talkChannel(11, 0, text) + else + return context.say(text) + end +end +context.sayNpc = context.talkNpc +context.sayNPC = context.talkNpc +context.talkNPC = context.talkNpc + +context.saySpell = function(text, lastSpellTimeout) + if not text or text:len() < 1 then + return + end + if context.lastSpell == nil then + context.lastSpell = 0 + end + if not lastSpellTimeout then + lastSpellTimeout = 1000 + end + if context.lastSpell + lastSpellTimeout > context.now then + return false + end + context.say(text) + context.lastSpell = context.now + return true +end + +context.setSpellTimeout = function() + context.lastSpell = context.now +end + +context.use = function(thing, subtype) + if type(thing) == 'number' then + return g_game.useInventoryItem(thing, subtype) + else + return g_game.use(thing) + end +end +context.usewith = function(thing, target, subtype) + if type(thing) == 'number' then + return g_game.useInventoryItemWith(thing, target, subtype) + else + return g_game.useWith(thing, target, subtype) + end +end +context.useWith = context.usewith + +context.useRune = function(itemid, target, lastSpellTimeout) + if context.lastRuneUse == nil then + context.lastRuneUse = 0 + end + if not lastRuneTimeout then + lastRuneTimeout = 1000 + end + if context.lastRuneUse + lastRuneTimeout > context.now then + return false + end + context.usewith(itemid, target) + context.lastRuneUse = context.now + return true +end +context.userune = context.useRune + +context.findItem = function(itemId, subType) + if subType == nil then + subType = -1 + end + return g_game.findItemInContainers(itemId, subType) +end + +context.attack = g_game.attack +context.cancelAttack = g_game.cancelAttack +context.follow = g_game.follow +context.cancelFollow = g_game.cancelFollow +context.cancelAttackAndFollow = g_game.cancelAttackAndFollow + +context.logout = g_game.forceLogout +context.safeLogout = g_game.safeLogout +context.ping = g_game.getPing + +modules.game_cooldown.isGroupCooldownIconActive(id) +modules.game_cooldown.isCooldownIconActive(id) + diff --git a/800OTClient/modules/game_bot/functions/player_conditions.lua b/800OTClient/modules/game_bot/functions/player_conditions.lua new file mode 100644 index 0000000..b87ceac --- /dev/null +++ b/800OTClient/modules/game_bot/functions/player_conditions.lua @@ -0,0 +1,32 @@ +local context = G.botContext + +for i, state in ipairs(PlayerStates) do + context[state] = state +end + +context.hasCondition = function(condition) return bit.band(context.player:getStates(), condition) > 0 end + +context.isPoisioned = function() return context.hasCondition(PlayerStates.Poison) end +context.isBurning = function() return context.hasCondition(PlayerStates.Burn) end +context.isEnergized = function() return context.hasCondition(PlayerStates.Energy) end +context.isDrunk = function() return context.hasCondition(PlayerStates.Drunk) end +context.hasManaShield = function() return context.hasCondition(PlayerStates.ManaShield) end +context.isParalyzed = function() return context.hasCondition(PlayerStates.Paralyze) end +context.hasHaste = function() return context.hasCondition(PlayerStates.Haste) end +context.hasSwords = function() return context.hasCondition(PlayerStates.Swords) end +context.isInFight = function() return context.hasCondition(PlayerStates.Swords) end +context.canLogout = function() return not context.hasCondition(PlayerStates.Swords) end +context.isDrowning = function() return context.hasCondition(PlayerStates.Drowning) end +context.isFreezing = function() return context.hasCondition(PlayerStates.Freezing) end +context.isDazzled = function() return context.hasCondition(PlayerStates.Dazzled) end +context.isCursed = function() return context.hasCondition(PlayerStates.Cursed) end +context.hasPartyBuff = function() return context.hasCondition(PlayerStates.PartyBuff) end +context.hasPzLock = function() return context.hasCondition(PlayerStates.PzBlock) end +context.hasPzBlock = function() return context.hasCondition(PlayerStates.PzBlock) end +context.isPzLocked = function() return context.hasCondition(PlayerStates.PzBlock) end +context.isPzBlocked = function() return context.hasCondition(PlayerStates.PzBlock) end +context.isInProtectionZone = function() return context.hasCondition(PlayerStates.Pz) end +context.hasPz = function() return context.hasCondition(PlayerStates.Pz) end +context.isInPz = function() return context.hasCondition(PlayerStates.Pz) end +context.isBleeding = function() return context.hasCondition(PlayerStates.Bleeding) end +context.isHungry = function() return context.hasCondition(PlayerStates.Hungry) end diff --git a/800OTClient/modules/game_bot/functions/player_inventory.lua b/800OTClient/modules/game_bot/functions/player_inventory.lua new file mode 100644 index 0000000..9ea8c4c --- /dev/null +++ b/800OTClient/modules/game_bot/functions/player_inventory.lua @@ -0,0 +1,45 @@ +local context = G.botContext + +context.SlotOther = InventorySlotOther +context.SlotHead = InventorySlotHead +context.SlotNeck = InventorySlotNeck +context.SlotBack = InventorySlotBack +context.SlotBody = InventorySlotBody +context.SlotRight = InventorySlotRight +context.SlotLeft = InventorySlotLeft +context.SlotLeg = InventorySlotLeg +context.SlotFeet = InventorySlotFeet +context.SlotFinger = InventorySlotFinger +context.SlotAmmo = InventorySlotAmmo +context.SlotPurse = InventorySlotPurse + +context.getInventoryItem = function(slot) return context.player:getInventoryItem(slot) end +context.getSlot = context.getInventoryItem + +context.getHead = function() return context.getInventoryItem(context.SlotHead) end +context.getNeck = function() return context.getInventoryItem(context.SlotNeck) end +context.getBack = function() return context.getInventoryItem(context.SlotBack) end +context.getBody = function() return context.getInventoryItem(context.SlotBody) end +context.getRight = function() return context.getInventoryItem(context.SlotRight) end +context.getLeft = function() return context.getInventoryItem(context.SlotLeft) end +context.getLeg = function() return context.getInventoryItem(context.SlotLeg) end +context.getFeet = function() return context.getInventoryItem(context.SlotFeet) end +context.getFinger = function() return context.getInventoryItem(context.SlotFinger) end +context.getAmmo = function() return context.getInventoryItem(context.SlotAmmo) end +context.getPurse = function() return context.getInventoryItem(context.SlotPurse) end + +context.getContainers = function() return g_game.getContainers() end +context.getContainer = function(index) return g_game.getContainer(index) end + +context.moveToSlot = function(item, slot, count) + if type(item) == 'number' then + item = context.findItem(item) + end + if not item then + return + end + if count == nil then + count = item:getCount() + end + return g_game.move(item, {x=65535, y=slot, z=0}, count) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/functions/script_loader.lua b/800OTClient/modules/game_bot/functions/script_loader.lua new file mode 100644 index 0000000..edac0dc --- /dev/null +++ b/800OTClient/modules/game_bot/functions/script_loader.lua @@ -0,0 +1,59 @@ +local context = G.botContext + +context.loadScript = function(path, onLoadCallback) + if type(path) ~= 'string' then + return context.error("Invalid path for loadScript: " .. tostring(path)) + end + if path:lower():find("http") == 1 then + return context.loadRemoteScript(path) + end + if not g_resources.fileExists(path) then + return context.error("File " .. path .. " doesn't exist") + end + + local status, result = pcall(function() + assert(load(g_resources.readFileContents(path), path, nil, context))() + end) + if not status then + return context.error("Error while loading script from: " .. path .. ":\n" .. result) + end + if onLoadCallback then + onLoadCallback() + end +end + +context.loadRemoteScript = function(url, onLoadCallback) + if type(url) ~= 'string' or url:lower():find("http") ~= 1 then + return context.error("Invalid url for loadRemoteScript: " .. tostring(url)) + end + + HTTP.get(url, function(data, err) + if err or data:len() == 0 then + -- try to load from cache + if type(context.storage.scriptsCache) ~= 'table' then + context.storage.scriptsCache = {} + end + local cache = context.storage.scriptsCache[url] + if cache and type(cache) == 'string' and cache:len() > 0 then + data = cache + else + return context.error("Can't load script from: " .. url .. ", error: " .. err) + end + end + + local status, result = pcall(function() + assert(load(data, url, nil, context))() + end) + if not status then + return context.error("Error while loading script from: " .. url .. ":\n" .. result) + end + -- cache script + if type(context.storage.scriptsCache) ~= 'table' then + context.storage.scriptsCache = {} + end + context.storage.scriptsCache[url] = data + if onLoadCallback then + onLoadCallback() + end + end) +end diff --git a/800OTClient/modules/game_bot/functions/server.lua b/800OTClient/modules/game_bot/functions/server.lua new file mode 100644 index 0000000..f620fc5 --- /dev/null +++ b/800OTClient/modules/game_bot/functions/server.lua @@ -0,0 +1,91 @@ +local context = G.botContext + +context.BotServer = {} +context.BotServer.url = "ws://bot.otclient.ovh:8000/" +context.BotServer.timeout = 3 +context.BotServer.ping = 0 +context.BotServer._callbacks = {} +context.BotServer._lastMessageId = 0 +context.BotServer._wasConnected = true -- show first warning + +context.BotServer.init = function(name, channel) + if not channel or not name or channel:len() < 1 or name:len() < 1 then + return context.error("Invalid params for BotServer.init") + end + if context.BotServer._websocket then + return context.error("BotServer is already initialized") + end + context.BotServer._websocket = HTTP.WebSocketJSON(context.BotServer.url, { + onMessage = function(message, socketId) + if not context._websockets[socketId] then + return g_http.cancel(socketId) + end + if not context.BotServer._websocket or context.BotServer._websocket.id ~= socketId then + return g_http.cancel(socketId) + end + context.BotServer._wasConnected = true + if message["type"] == "ping" then + context.BotServer.ping = message["ping"] + return context.BotServer._websocket.send({type="ping"}) + end + if message["type"] == "message" then + context.BotServer._lastMessageId = message["id"] + local topics = context.BotServer._callbacks[message["topic"]] + if topics then + for i=1,#topics do + topics[i](message["name"], message["message"], message["topic"]) + end + end + topics = context.BotServer._callbacks["*"] + if topics then + for i=1,#topics do + topics[i](message["name"], message["message"], message["topic"]) + end + end + return + end + end, + onClose = function(message, socketId) + if not context._websockets[socketId] then + return + end + context._websockets[socketId] = nil + if not context.BotServer._websocket or context.BotServer._websocket.id ~= socketId then + return + end + if context.BotServer._wasConnected then + context.warn("BotServer disconnected") + end + context.BotServer._wasConnected = false + context.BotServer._websocket = nil + context.BotServer.ping = 0 + context.BotServer.init(name, channel) + end + }, context.BotServer.timeout) + context._websockets[context.BotServer._websocket.id] = 1 + context.BotServer._websocket.send({type="init", name=name, channel=channel, lastMessage=context.BotServer._lastMessageId}) +end + +context.BotServer.terminate = function() + if context.BotServer._websocket then + context.BotServer._websocket:close() + context.BotServer._websocket = nil + end +end + +context.BotServer.listen = function(topic, callback) -- callback = function(name, message, topic) -- message is parsed json = table + if not context.BotServer._websocket then + return context.error("BotServer is not initialized") + end + if not context.BotServer._callbacks[topic] then + context.BotServer._callbacks[topic] = {} + end + table.insert(context.BotServer._callbacks[topic], callback) +end + +context.BotServer.send = function(topic, message) + if not context.BotServer._websocket then + return context.error("BotServer is not initialized") + end + context.BotServer._websocket.send({type="message", topic=topic, message=message}) +end diff --git a/800OTClient/modules/game_bot/functions/sound.lua b/800OTClient/modules/game_bot/functions/sound.lua new file mode 100644 index 0000000..c7fffee --- /dev/null +++ b/800OTClient/modules/game_bot/functions/sound.lua @@ -0,0 +1,31 @@ +local context = G.botContext + +context.getSoundChannel = function() + if not g_sounds then + return + end + return g_sounds.getChannel(SoundChannels.Bot) +end + +context.playSound = function(file) + local botSoundChannel = context.getSoundChannel() + if not botSoundChannel then + return + end + botSoundChannel:setEnabled(true) + botSoundChannel:stop(0) + botSoundChannel:play(file, 0, 1.0) + return botSoundChannel +end + +context.stopSound = function() + local botSoundChannel = context.getSoundChannel() + if not botSoundChannel then + return + end + botSoundChannel:stop() +end + +context.playAlarm = function() + return context.playSound("/sounds/alarm.ogg") +end diff --git a/800OTClient/modules/game_bot/functions/test.lua b/800OTClient/modules/game_bot/functions/test.lua new file mode 100644 index 0000000..9d6afcf --- /dev/null +++ b/800OTClient/modules/game_bot/functions/test.lua @@ -0,0 +1,3 @@ +local context = G.botContext + +context.test = function() return context.info("test") end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/functions/tools.lua b/800OTClient/modules/game_bot/functions/tools.lua new file mode 100644 index 0000000..bebae76 --- /dev/null +++ b/800OTClient/modules/game_bot/functions/tools.lua @@ -0,0 +1,19 @@ +local context = G.botContext + +context.encode = function(data, indent) return json.encode(data, indent or 2) end +context.decode = function(text) local status, result = pcall(function() return json.decode(text) end) if status then return result end return {} end + +context.displayGeneralBox = function(title, message, buttons, onEnterCallback, onEscapeCallback) + local box = displayGeneralBox(title, message, buttons, onEnterCallback, onEscapeCallback) + box.botWidget = true + return box +end + +context.doScreenshot = function(filename) + g_app.doScreenshot(filename) +end +context.screenshot = context.doScreenshot + +context.getVersion = function() + return g_app.getVersion() +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/functions/ui.lua b/800OTClient/modules/game_bot/functions/ui.lua new file mode 100644 index 0000000..fe020db --- /dev/null +++ b/800OTClient/modules/game_bot/functions/ui.lua @@ -0,0 +1,33 @@ +local context = G.botContext +if type(context.UI) ~= "table" then + context.UI = {} +end +local UI = context.UI + +UI.createWidget = function(name, parent) + if parent == nil then + parent = context.panel + end + local widget = g_ui.createWidget(name, parent) + widget.botWidget = true + return widget +end + +UI.createMiniWindow = function(name, parent) + if parent == nil then + parent = modules.game_interface.getRightPanel() + end + local widget = g_ui.createWidget(name, parent) + widget:setup() + widget.botWidget = true + return widget +end + +UI.createWindow = function(name) + local widget = g_ui.createWidget(name, g_ui.getRootWidget()) + widget.botWidget = true + widget:show() + widget:raise() + widget:focus() + return widget +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/functions/ui_elements.lua b/800OTClient/modules/game_bot/functions/ui_elements.lua new file mode 100644 index 0000000..34d5b80 --- /dev/null +++ b/800OTClient/modules/game_bot/functions/ui_elements.lua @@ -0,0 +1,401 @@ +local context = G.botContext +if type(context.UI) ~= "table" then + context.UI = {} +end +local UI = context.UI + +UI.Button = function(text, callback, parent) + local widget = UI.createWidget("BotButton", parent) + widget:setText(text) + widget.onClick = callback + return widget +end + + +UI.Config = function(parent) + return UI.createWidget("BotConfig", parent) +end + +-- call :setItems(table) to set items, call :getItems() to get them +-- unique if true, won't allow duplicates +-- callback (can be nil) gets table with new item list, eg: {{id=2160, count=1}, {id=268, count=100}, {id=269, count=20}} +UI.Container = function(callback, unique, parent, widget) + if not widget then + widget = UI.createWidget("BotContainer", parent) + end + + local oldItems = {} + + local updateItems = function() + local items = widget:getItems() + + -- callback part + local somethingNew = (#items ~= #oldItems) + for i, item in ipairs(items) do + if type(oldItems[i]) ~= "table" then + somethingNew = true + break + end + if oldItems[i].id ~= item.id or oldItems[i].count ~= item.count then + somethingNew = true + break + end + end + + if somethingNew then + oldItems = items + callback(widget, items) + end + + widget:setItems(items) + end + + widget.setItems = function(self, items) + if type(self) == 'table' then + items = self + end + local itemsToShow = math.max(10, #items + 2) + if itemsToShow % 5 ~= 0 then + itemsToShow = itemsToShow + 5 - itemsToShow % 5 + end + widget.items:destroyChildren() + for i = 1, itemsToShow do + local widget = g_ui.createWidget("BotItem", widget.items) + if type(items[i]) == 'number' then + items[i] = {id=items[i], count=1} + end + if type(items[i]) == 'table' then + widget:setItem(Item.create(items[i].id, items[i].count)) + end + end + oldItems = items + for i, child in ipairs(widget.items:getChildren()) do + child.onItemChange = updateItems + end + end + + widget.getItems = function() + local items = {} + local duplicates = {} + for i, child in ipairs(widget.items:getChildren()) do + if child:getItemId() >= 100 then + if not duplicates[child:getItemId()] or not unique then + table.insert(items, {id=child:getItemId(), count=child:getItemCountOrSubType()}) + duplicates[child:getItemId()] = true + end + end + end + return items + end + + widget:setItems({}) + + return widget +end + +UI.DualScrollPanel = function(params, callback, parent) -- callback = function(widget, newParams) + --[[ params: + on - bool, + text - string, + title - string, + min - number, + max - number, + ]] + params.title = params.title or "title" + params.text = params.text or "" + params.min = params.min or 20 + params.max = params.max or 80 + + local widget = UI.createWidget('DualScrollPanel', parent) + + widget.title:setOn(params.on) + widget.title.onClick = function() + params.on = not params.on + widget.title:setOn(params.on) + if callback then + callback(widget, params) + end + end + + widget.text:setText(params.text or "") + widget.text.onTextChange = function(widget, text) + params.text = text + if callback then + callback(widget, params) + end + end + + local update = function(dontSignal) + widget.title:setText("" .. params.min .. "% <= " .. params.title .. " <= " .. params.max .. "%") + if callback and not dontSignal then + callback(widget, params) + end + end + + widget.scroll1:setValue(params.min) + widget.scroll2:setValue(params.max) + + widget.scroll1.onValueChange = function(scroll, value) + params.min = value + update() + end + widget.scroll2.onValueChange = function(scroll, value) + params.max = value + update() + end + update(true) +end + +UI.DualScrollItemPanel = function(params, callback, parent) -- callback = function(widget, newParams) + --[[ params: + on - bool, + item - number, + subType - number, + title - string, + min - number, + max - number, + ]] + params.title = params.title or "title" + params.item = params.item or 0 + params.subType = params.subType or 0 + params.min = params.min or 20 + params.max = params.max or 80 + + local widget = UI.createWidget('DualScrollItemPanel', parent) + + widget.title:setOn(params.on) + widget.title.onClick = function() + params.on = not params.on + widget.title:setOn(params.on) + if callback then + callback(widget, params) + end + end + + widget.item:setItem(Item.create(params.item, params.subType)) + widget.item.onItemChange = function() + params.item = widget.item:getItemId() + params.subType = widget.item:getItemSubType() + if callback then + callback(widget, params) + end + end + + local update = function(dontSignal) + widget.title:setText("" .. params.min .. "% <= " .. params.title .. " <= " .. params.max .. "%") + if callback and not dontSignal then + callback(widget, params) + end + end + + widget.scroll1:setValue(params.min) + widget.scroll2:setValue(params.max) + + widget.scroll1.onValueChange = function(scroll, value) + params.min = value + update() + end + widget.scroll2.onValueChange = function(scroll, value) + params.max = value + update() + end + update(true) +end + +UI.Label = function(text, parent) + local label = UI.createWidget('BotLabel', parent) + label:setText(text) + return label +end + +UI.Separator = function(parent) + local separator = UI.createWidget('BotSeparator', parent) + return separator +end + +UI.TextEdit = function(text, callback, parent) + local widget = UI.createWidget('BotTextEdit', parent) + widget.onTextChange = callback + widget:setText(text) + return widget +end + +UI.TwoItemsAndSlotPanel = function(params, callback, parent) + --[[ params: + on - bool, + title - string, + item1 - number, + item2 - number, + slot - number, + ]] + params.title = params.title or "title" + params.item1 = params.item1 or 0 + params.item2 = params.item2 or 0 + params.slot = params.slot or 1 + + local widget = UI.createWidget("TwoItemsAndSlotPanel", parent) + + widget.title:setText(params.title) + widget.title:setOn(params.on) + widget.title.onClick = function() + params.on = not params.on + widget.title:setOn(params.on) + if callback then + callback(widget, params) + end + end + + widget.slot:setCurrentIndex(params.slot) + widget.slot.onOptionChange = function() + params.slot = widget.slot.currentIndex + if callback then + callback(widget, params) + end + end + + widget.item1:setItemId(params.item1) + widget.item1.onItemChange = function() + params.item1 = widget.item1:getItemId() + if callback then + callback(widget, params) + end + end + + widget.item2:setItemId(params.item2) + widget.item2.onItemChange = function() + params.item2 = widget.item2:getItemId() + if callback then + callback(widget, params) + end + end + + return widget +end + +UI.DualLabel = function(left, right, params, parent) + --[[ params: + height - int, + maxWidth - number + ]] + + left = left or "" + right = right or "" + params = params or {} + if not type(params) == "table" then + parent = params + params = {} + end + params.height = params.height or 20 + params.maxWidth = params.maxWidth or 88 + + local widget = UI.createWidget('DualLabelPanel', parent) + + widget.left:setText(left) + widget.right:setText(right) + widget:setHeight(params.height) + if widget.left:getWidth() > params.maxWidth then + widget.left:setWidth(params.maxWidth) + end + return widget +end + +UI.LabelAndTextEdit = function(params, callback, parent) + --[[ params: + left - str, + right - str, + height - int, + maxWidth - int, + ]] + + params = params or {} + params.left = params.left or "" + params.right = params.right or "" + params.height = params.height or 20 + params.maxWidth = params.maxWidth or 88 + + local widget = UI.createWidget('LabelAndTextEditPanel', parent) + + widget.left:setText(params.left) + widget.right:setText(params.right) + widget:setHeight(params.height) + if widget.left:getWidth() > params.maxWidth then + widget.left:setWidth(params.maxWidth) + end + + widget.right.onTextChange = function(widget, text) + params.right = text + if callback then + callback(widget, params) + end + end + + --[[example: + + storage.testParams = storage.testParams or {left = "hotkey", right = "F5"} + UI.LabelAndTextEdit(storage.testParams, function(widget, newParams) + storage.testParams = newParams + end) + + ]] + return widget +end + + +UI.SwitchAndButton = function(params, callbackSwitch, callbackButton, callback, parent) + --[[ params: + on - bool, + left - str, + right - str, + height - int, + maxWidth - int, + ]] + + params = params or {} + params.on = params.on or false + params.left = params.left or "" + params.right = params.right or "" + params.height = params.height or 20 + params.maxWidth = params.maxWidth or 88 + + local widget = UI.createWidget('SwitchAndButtonPanel', parent) + + widget.left:setOn(params.on) + widget.left:setText(params.left) + widget.right:setText(params.right) + widget:setHeight(params.height) + if widget.right:getWidth() > params.maxWidth then + widget.right:setWidth(params.maxWidth) + end + + widget.left.onClick = function() + params.on = not params.on + widget.left:setOn(params.on) + if callback then + callback(widget, params) + end + if callbackSwitch then + callbackSwitch() + else + warn("callback not set!") + end + end + + widget.right.onClick = function() + if callbackButton then + callbackButton() + else + warn("callback not set!") + end + end + + --[[ params: + if type(storage.test1) ~= "table" then + storage.test1 = storage.test1 or {on = false, left = "new script", right = "config"} + end + + UI.SwitchAndButton(storage.test1, test, test, function(widget, newParams) + storage.test1 = newParams + end) + ]] + return widget +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/functions/ui_legacy.lua b/800OTClient/modules/game_bot/functions/ui_legacy.lua new file mode 100644 index 0000000..2fece4c --- /dev/null +++ b/800OTClient/modules/game_bot/functions/ui_legacy.lua @@ -0,0 +1,135 @@ +local context = G.botContext + +-- DO NOT USE THIS CODE. +-- IT'S ONLY HERE FOR BACKWARD COMPATIBILITY, MAY BE REMOVED IN THE FUTURE + +context.createWidget = function(name, parent) + if parent == nil then + parent = context.panel + end + g_ui.createWidget(name, parent) +end + +context.setupUI = function(otml, parent) + if parent == nil then + parent = context.panel + end + local widget = g_ui.loadUIFromString(otml, parent) + widget.botWidget = true + return widget +end + +context.importStyle = function(otml) + if type(otml) ~= "string" then + return error("Invalid parameter for importStyle, should be string") + end + if otml:find(".otui") and not otml:find("\n") then + return g_ui.importStyle(context.configDir .. "/" .. otml) + end + return g_ui.importStyleFromString(otml) +end + +context.addTab = function(name) + local tab = context.tabs:getTab(name) + if tab then -- return existing tab + return tab.tabPanel.content + end + + local smallTabs = #(context.tabs.tabs) >= 5 + local newTab = context.tabs:addTab(name, g_ui.createWidget('BotPanel')).tabPanel.content + context.tabs:setOn(true) + if smallTabs then + for k,tab in pairs(context.tabs.tabs) do + tab:setFont('small-9px') + end + end + + return newTab +end +context.getTab = context.addTab + +context.setDefaultTab = function(name) + local tab = context.addTab(name) + context.panel = tab +end + +context.addSwitch = function(id, text, onClickCallback, parent) + if not parent then + parent = context.panel + end + local switch = g_ui.createWidget('BotSwitch', parent) + switch:setId(id) + switch:setText(text) + switch.onClick = onClickCallback + return switch +end + +context.addButton = function(id, text, onClickCallback, parent) + if not parent then + parent = context.panel + end + local button = g_ui.createWidget('BotButton', parent) + button:setId(id) + button:setText(text) + button.onClick = onClickCallback + return button +end + +context.addLabel = function(id, text, parent) + if not parent then + parent = context.panel + end + local label = g_ui.createWidget('BotLabel', parent) + label:setId(id) + label:setText(text) + return label +end + +context.addTextEdit = function(id, text, onTextChangeCallback, parent) + if not parent then + parent = context.panel + end + local widget = g_ui.createWidget('BotTextEdit', parent) + widget:setId(id) + widget.onTextChange = onTextChangeCallback + widget:setText(text) + return widget +end + +context.addSeparator = function(id, parent) + if not parent then + parent = context.panel + end + local separator = g_ui.createWidget('BotSeparator', parent) + separator:setId(id) + return separator +end + +context._addMacroSwitch = function(name, keys, parent) + if not parent then + parent = context.panel + end + local text = name + if keys:len() > 0 then + text = name .. " [" .. keys .. "]" + end + local switch = context.addSwitch("macro_" .. #context._macros, text, function(widget) + context.storage._macros[name] = not context.storage._macros[name] + widget:setOn(context.storage._macros[name]) + end, parent) + switch:setOn(context.storage._macros[name]) + return switch +end + +context._addHotkeySwitch = function(name, keys, parent) + if not parent then + parent = context.panel + end + local text = name + if keys:len() > 0 then + text = name .. " [" .. keys .. "]" + end + local switch = context.addSwitch("hotkey_" .. #context._hotkeys, text, nil, parent) + switch:setOn(false) + return switch +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/functions/ui_windows.lua b/800OTClient/modules/game_bot/functions/ui_windows.lua new file mode 100644 index 0000000..a0c0a4c --- /dev/null +++ b/800OTClient/modules/game_bot/functions/ui_windows.lua @@ -0,0 +1,49 @@ +local context = G.botContext +if type(context.UI) ~= "table" then + context.UI = {} +end +local UI = context.UI + +UI.EditorWindow = function(text, options, callback) + --[[ + Available options: + title = text + description = text + multiline = true / false + width = number + validation = text (regex) + examples = {{name, text}, {name, text}} + ]]-- + local window = modules.client_textedit.edit(text, options, callback) + window.botWidget = true + return window +end + +UI.SinglelineEditorWindow = function(text, options, callback) + options = options or {} + options.multiline = false + return UI.EditorWindow(text, options, callback) +end + +UI.MultilineEditorWindow = function(text, options, callback) + options = options or {} + options.multiline = true + return UI.EditorWindow(text, options, callback) +end + +UI.ConfirmationWindow = function(title, question, callback) + local window = nil + local onConfirm = function() + window:destroy() + callback() + end + local closeWindow = function() + window:destroy() + end + window = context.displayGeneralBox(title, question, { + { text=tr('Yes'), callback=onConfirm }, + { text=tr('No'), callback=closeWindow }, + anchor=AnchorHorizontalCenter}, onConfirm, closeWindow) + window.botWidget = true + return window +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/panels/DONT_USE_PANELS.txt b/800OTClient/modules/game_bot/panels/DONT_USE_PANELS.txt new file mode 100644 index 0000000..a9c2c18 --- /dev/null +++ b/800OTClient/modules/game_bot/panels/DONT_USE_PANELS.txt @@ -0,0 +1,3 @@ +DONT USE PANELS +THEY ONLY HERE FOR BACKWARD COMPATIBILITY +MAY BE REMOVED IN THE FUTURE \ No newline at end of file diff --git a/800OTClient/modules/game_bot/panels/attacking.lua b/800OTClient/modules/game_bot/panels/attacking.lua new file mode 100644 index 0000000..1ffd37e --- /dev/null +++ b/800OTClient/modules/game_bot/panels/attacking.lua @@ -0,0 +1,1186 @@ +local context = G.botContext +local Panels = context.Panels + +Panels.MonsterEditor = function(monster, config, callback, parent) + local otherWindow = g_ui.getRootWidget():getChildById('monsterEditor') + if otherWindow then + otherWindow:destory() + end + + local window = context.setupUI([[ +MainWindow + id: monsterEditor + size: 450 450 + !text: tr("Edit monster") + + Label + id: info + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-align: center + text: Use monster name * for any other monster not on the list + + Label + id: info2 + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + text-align: center + text: Add number (1-5) at the end of the name to create multiple configs + + TextEdit + id: name + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-left: 100 + margin-top: 5 + multiline: false + + Label + anchors.verticalCenter: prev.verticalCenter + anchors.left: parent.left + text: Target name: + + Label + id: priorityText + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-right: 10 + margin-top: 10 + text: Priority + text-align: center + + HorizontalScrollBar + id: priority + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 0 + maximum: 10 + step: 1 + + Label + id: dangerText + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-right: 10 + margin-top: 10 + text: Danger + text-align: center + + HorizontalScrollBar + id: danger + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 0 + maximum: 10 + step: 1 + + Label + id: maxDistanceText + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-right: 10 + margin-top: 10 + text: Max distance to target + text-align: center + + HorizontalScrollBar + id: maxDistance + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 1 + maximum: 10 + step: 1 + + Label + id: distanceText + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-right: 10 + margin-top: 10 + text: Keep distance + text-align: center + + HorizontalScrollBar + id: distance + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 0 + maximum: 5 + step: 1 + + Label + id: minHealthText + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-right: 10 + margin-top: 10 + text: Minimum Health + text-align: center + + HorizontalScrollBar + id: minHealth + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 0 + maximum: 100 + step: 1 + + Label + id: maxHealthText + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-right: 10 + margin-top: 10 + text: Maximum Health + text-align: center + + HorizontalScrollBar + id: maxHealth + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 5 + minimum: 0 + maximum: 100 + step: 1 + + Label + id: dangerText + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-right: 5 + margin-top: 10 + text: If total danger is high (>8) bot won't auto loot until it's low again and will be trying to minimize it + text-align: center + text-wrap: true + text-auto-resize: true + + Label + id: attackSpellText + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-right: 5 + margin-top: 10 + text: Attack spell and attack rune are only used when you have more than 30% health + text-align: center + text-wrap: true + text-auto-resize: true + + BotSwitch + id: attack + anchors.left: parent.horizontalCenter + anchors.top: name.bottom + margin-left: 10 + margin-top: 10 + width: 55 + text: Attack + + BotSwitch + id: ignore + anchors.left: prev.right + anchors.top: name.bottom + margin-left: 18 + margin-top: 10 + width: 55 + text: Ignore + + BotSwitch + id: avoid + anchors.left: prev.right + anchors.top: name.bottom + margin-left: 18 + margin-top: 10 + width: 55 + text: Avoid + + BotSwitch + id: keepDistance + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: prev.bottom + margin-left: 10 + margin-top: 10 + text: Keep distance + + BotSwitch + id: avoidAttacks + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: prev.bottom + margin-left: 10 + margin-top: 10 + text: Avoid monster attacks + + BotSwitch + id: chase + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: prev.bottom + margin-left: 10 + margin-top: 10 + text: Chase when has low health + + BotSwitch + id: loot + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: prev.bottom + margin-left: 10 + margin-top: 10 + text: Loot corpse + + BotSwitch + id: monstersOnly + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: prev.bottom + margin-left: 10 + margin-top: 10 + text: Only for monsters + + BotSwitch + id: dontWalk + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: prev.bottom + margin-left: 10 + margin-top: 10 + text: Don't walk to target + + Label + id: attackSpellText + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: prev.bottom + margin-left: 10 + margin-top: 10 + text: Attack Spell: + text-align: center + + TextEdit + id: attackSpell + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 2 + + Label + id: attackItemText + anchors.left: parent.horizontalCenter + anchors.top: prev.bottom + margin-top: 20 + margin-left: 20 + text: Attack rune: + text-align: left + + BotItem + id: attackItem + anchors.right: parent.right + anchors.verticalCenter: prev.verticalCenter + margin-right: 30 + + Button + id: okButton + !text: tr('Ok') + anchors.bottom: parent.bottom + anchors.right: next.left + margin-right: 10 + width: 60 + + Button + id: cancelButton + !text: tr('Cancel') + anchors.bottom: parent.bottom + anchors.right: parent.right + width: 60 +]], g_ui.getRootWidget()) + + local destroy = function() + window:destroy() + end + local doneFunc = function() + local monster = window.name:getText() + local config = { + priority = window.priority:getValue(), + danger = window.danger:getValue(), + maxDistance = window.maxDistance:getValue(), + distance = window.distance:getValue(), + minHealth = window.minHealth:getValue(), + maxHealth = window.maxHealth:getValue(), + attack = window.attack:isOn(), + ignore = window.ignore:isOn(), + avoid = window.avoid:isOn(), + keepDistance = window.keepDistance:isOn(), + avoidAttacks = window.avoidAttacks:isOn(), + chase = window.chase:isOn(), + loot = window.loot:isOn(), + monstersOnly = window.monstersOnly:isOn(), + dontWalk = window.dontWalk:isOn(), + attackItem = window.attackItem:getItemId(), + attackSpell = window.attackSpell:getText() + } + destroy() + callback(monster, config) + end + + window.okButton.onClick = doneFunc + window.cancelButton.onClick = destroy + window.onEnter = doneFunc + window.onEscape = destroy + + + window.priority.onValueChange = function(scroll, value) + window.priorityText:setText("Priority: " .. value) + end + window.danger.onValueChange = function(scroll, value) + window.dangerText:setText("Danger: " .. value) + end + window.maxDistance.onValueChange = function(scroll, value) + window.maxDistanceText:setText("Max distance to target: " .. value) + end + window.distance.onValueChange = function(scroll, value) + window.distanceText:setText("Keep distance: " .. value) + end + window.minHealth.onValueChange = function(scroll, value) + window.minHealthText:setText("Minimum health: " .. value .. "%") + end + window.maxHealth.onValueChange = function(scroll, value) + window.maxHealthText:setText("Maximum health: " .. value .. "%") + end + + window.priority:setValue(config.priority or 1) + window.danger:setValue(config.danger or 1) + window.maxDistance:setValue(config.maxDistance or 6) + window.distance:setValue(config.distance or 1) + window.minHealth:setValue(1) -- to force onValueChange update + window.maxHealth:setValue(1) -- to force onValueChange update + window.minHealth:setValue(config.minHealth or 0) + window.maxHealth:setValue(config.maxHealth or 100) + + window.attackSpell:setText(config.attackSpell or "") + window.attackItem:setItemId(config.attackItem or 0) + + window.attack.onClick = function(widget) + if widget:isOn() then + return + end + widget:setOn(true) + window.ignore:setOn(false) + window.avoid:setOn(false) + end + window.ignore.onClick = function(widget) + if widget:isOn() then + return + end + widget:setOn(true) + window.attack:setOn(false) + window.avoid:setOn(false) + end + window.avoid.onClick = function(widget) + if widget:isOn() then + return + end + widget:setOn(true) + window.attack:setOn(false) + window.ignore:setOn(false) + end + + window.attack:setOn(config.attack) + window.ignore:setOn(config.ignore) + window.avoid:setOn(config.avoid) + if not window.attack:isOn() and not window.ignore:isOn() and not window.avoid:isOn() then + window.attack:setOn(true) + end + + window.keepDistance.onClick = function(widget) + widget:setOn(not widget:isOn()) + end + window.avoidAttacks.onClick = function(widget) + widget:setOn(not widget:isOn()) + end + window.chase.onClick = function(widget) + widget:setOn(not widget:isOn()) + end + window.loot.onClick = function(widget) + widget:setOn(not widget:isOn()) + end + window.monstersOnly.onClick = function(widget) + widget:setOn(not widget:isOn()) + end + window.dontWalk.onClick = function(widget) + widget:setOn(not widget:isOn()) + end + + window.keepDistance:setOn(config.keepDistance) + window.avoidAttacks:setOn(config.avoidAttacks) + window.chase:setOn(config.chase) + window.loot:setOn(config.loot) + if config.loot == nil then + window.loot:setOn(true) + end + window.monstersOnly:setOn(config.monstersOnly) + if config.monstersOnly == nil then + window.monstersOnly:setOn(true) + end + window.dontWalk:setOn(config.dontWalk) + + window.name:setText(monster) + + window:show() + window:raise() + window:focus() +end + +Panels.Attacking = function(parent) + local ui = context.setupUI([[ +Panel + id: attacking + height: 140 + + BotLabel + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text: Attacking + + ComboBox + id: config + anchors.top: prev.bottom + anchors.left: parent.left + margin-top: 5 + text-offset: 3 0 + width: 130 + + Button + id: enableButton + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 5 + + Button + margin-top: 1 + id: add + anchors.top: prev.bottom + anchors.left: parent.left + text: Add + width: 60 + height: 17 + + Button + id: edit + anchors.top: prev.top + anchors.horizontalCenter: parent.horizontalCenter + text: Edit + width: 60 + height: 17 + + Button + id: remove + anchors.top: prev.top + anchors.right: parent.right + text: Remove + width: 60 + height: 17 + + TextList + id: list + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + vertical-scrollbar: listScrollbar + margin-right: 15 + margin-top: 2 + height: 60 + focusable: false + auto-focus: first + + VerticalScrollBar + id: listScrollbar + anchors.top: prev.top + anchors.bottom: prev.bottom + anchors.right: parent.right + pixels-scroll: true + step: 5 + + Button + margin-top: 2 + id: mAdd + anchors.top: prev.bottom + anchors.left: parent.left + text: Add + width: 60 + height: 17 + + Button + id: mEdit + anchors.top: prev.top + anchors.horizontalCenter: parent.horizontalCenter + text: Edit + width: 60 + height: 17 + + Button + id: mRemove + anchors.top: prev.top + anchors.right: parent.right + text: Remove + width: 60 + height: 17 + +]], parent) + + if type(context.storage.attacking) ~= "table" then + context.storage.attacking = {} + end + if type(context.storage.attacking.configs) ~= "table" then + context.storage.attacking.configs = {} + end + + local getConfigName = function(config) + local matches = regexMatch(config, [[name:\s*([^\n]*)$]]) + if matches[1] and matches[1][2] then + return matches[1][2]:trim() + end + return nil + end + + local commands = {} + local monsters = {} + local configName = nil + local refreshConfig = nil -- declared later + + local createNewConfig = function() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + + local newConfig = "" + if configName ~= nil then + newConfig = "name:" .. configName .. "\n" + end + for monster, config in pairs(monsters) do + newConfig = newConfig .. "\n" .. monster .. ":" .. json.encode(config, 2) .. "\n" + end + + context.storage.attacking.configs[context.storage.attacking.activeConfig] = newConfig + refreshConfig() + end + + local parseConfig = function(config) + commands = {} + monsters = {} + configName = nil + + local matches = regexMatch(config, [[([^:^\n]+)(:?)([^\n]*)]]) + for i=1,#matches do + local command = matches[i][2] + local validation = (matches[i][3] == ":") + local text = matches[i][4] + if validation then + table.insert(commands, {command=command:lower(), text=text}) + elseif #commands > 0 then + commands[#commands].text = commands[#commands].text .. "\n" .. matches[i][1] + end + end + local labels = {} + for i, command in ipairs(commands) do + if commands[i].command == "name" then + configName = commands[i].text + else + local status, result = pcall(function() return json.decode(command.text) end) + if not status then + context.error("Invalid monster config: " .. commands[i].command .. ", error: " .. result) + else + monsters[commands[i].command] = result + table.insert(labels, commands[i].command) + end + end + end + table.sort(labels) + for i, text in ipairs(labels) do + local label = g_ui.createWidget("CaveBotLabel", ui.list) + label:setText(text) + end + end + + local ignoreOnOptionChange = true + refreshConfig = function(scrollDown) + ignoreOnOptionChange = true + if context.storage.attacking.enabled then + ui.enableButton:setText("On") + ui.enableButton:setColor('#00AA00FF') + else + ui.enableButton:setText("Off") + ui.enableButton:setColor('#FF0000FF') + end + + ui.config:clear() + for i, config in ipairs(context.storage.attacking.configs) do + local name = getConfigName(config) + if not name then + name = "Unnamed config" + end + ui.config:addOption(name) + end + + if (not context.storage.attacking.activeConfig or context.storage.attacking.activeConfig == 0) and #context.storage.attacking.configs > 0 then + context.storage.attacking.activeConfig = 1 + end + + ui.list:destroyChildren() + + if context.storage.attacking.activeConfig and context.storage.attacking.configs[context.storage.attacking.activeConfig] then + ui.config:setCurrentIndex(context.storage.attacking.activeConfig) + parseConfig(context.storage.attacking.configs[context.storage.attacking.activeConfig]) + end + + context.saveConfig() + if scrollDown and ui.list:getLastChild() then + ui.list:focusChild(ui.list:getLastChild()) + end + + ignoreOnOptionChange = false + end + + + ui.config.onOptionChange = function(widget) + if not ignoreOnOptionChange then + context.storage.attacking.activeConfig = widget.currentIndex + refreshConfig() + end + end + ui.enableButton.onClick = function() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + context.storage.attacking.enabled = not context.storage.attacking.enabled + refreshConfig() + end + ui.add.onClick = function() + modules.client_textedit.multilineEditor("Target list editor", "name:Config name", function(newText) + table.insert(context.storage.attacking.configs, newText) + context.storage.attacking.activeConfig = #context.storage.attacking.configs + refreshConfig() + end) + end + ui.edit.onClick = function() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + modules.client_textedit.multilineEditor("Target list editor", context.storage.attacking.configs[context.storage.attacking.activeConfig], function(newText) + context.storage.attacking.configs[context.storage.attacking.activeConfig] = newText + refreshConfig() + end) + end + ui.remove.onClick = function() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + local questionWindow = nil + local closeWindow = function() + questionWindow:destroy() + end + local removeConfig = function() + closeWindow() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + context.storage.attacking.enabled = false + table.remove(context.storage.attacking.configs, context.storage.attacking.activeConfig) + context.storage.attacking.activeConfig = 0 + refreshConfig() + end + questionWindow = context.displayGeneralBox(tr('Remove config'), tr('Do you want to remove current attacking config?'), { + { text=tr('Yes'), callback=removeConfig }, + { text=tr('No'), callback=closeWindow }, + anchor=AnchorHorizontalCenter}, removeConfig, closeWindow) + end + + + ui.mAdd.onClick = function() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + Panels.MonsterEditor("", {}, function(name, config) + if name:len() > 0 then + monsters[name] = config + end + createNewConfig() + end, parent) + end + ui.mEdit.onClick = function() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + local monsterWidget = ui.list:getFocusedChild() + if not monsterWidget or not monsters[monsterWidget:getText()] then + return + end + Panels.MonsterEditor(monsterWidget:getText(), monsters[monsterWidget:getText()], function(name, config) + monsters[monsterWidget:getText()] = nil + if name:len() > 0 then + monsters[name] = config + end + createNewConfig() + end, parent) + end + ui.mRemove.onClick = function() + if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then + return + end + local monsterWidget = ui.list:getFocusedChild() + if not monsterWidget or not monsters[monsterWidget:getText()] then + return + end + monsters[monsterWidget:getText()] = nil + createNewConfig() + end + + refreshConfig() + + -- processing + local isConfigPassingConditions = function(monster, config) + if not config or type(config.priority) ~= 'number' or type(config.danger) ~= 'number' then + return false + end + + if not config.attack then + return false + end + + if monster:isPlayer() and (config.monstersOnly == true or config.monstersOnly == nil) then + return false + end + + local pos = context.player:getPosition() + local mpos = monster:getPosition() + local hp = monster:getHealthPercent() + + if config.minHealth > hp or config.maxHealth < hp then + return false + end + + local maxDistance = 5 + if type(config.maxDistance) == 'number' then + maxDistance = config.maxDistance + end + if config.chase and hp < 25 then + maxDistance = maxDistance + 2 + end + + local distance = math.max(math.abs(pos.x-mpos.x), math.abs(pos.y-mpos.y)) + if distance > maxDistance then + return false + end + + local pathTo = context.findPath(context.player:getPosition(), {x=mpos.x, y=mpos.y, z=mpos.z}, maxDistance + 2, { ignoreNonPathable = true, precision=1, allowOnlyVisibleTiles = true, ignoreCost = true }) + if not pathTo or #pathTo > maxDistance + 1 then + return false + end + return true + end + + local getMonsterConfig = function(monster) + local name = monster:getName():lower() + local hasConfig = false + hasConfig = hasConfig or (monsters[name] ~= nil) + if isConfigPassingConditions(monster, monsters[name]) then + return monsters[name] + end + for i=1, 5 do + hasConfig = hasConfig or (monsters[name .. i] ~= nil) + if isConfigPassingConditions(monster, monsters[name .. i]) then + return monsters[name .. i] + end + end + if not hasConfig and isConfigPassingConditions(monster, monsters["*"]) then + return monsters["*"] + end + return nil + end + + local calculatePriority = function(monster) + local priority = 0 + local config = getMonsterConfig(monster) + if not config then + return -1 + end + + local pos = context.player:getPosition() + local mpos = monster:getPosition() + local hp = monster:getHealthPercent() + local pathTo = context.findPath(context.player:getPosition(), {x=mpos.x, y=mpos.y, z=mpos.z}, 10, { ignoreNonPathable = true, ignoreLastCreature = true, precision=0, allowOnlyVisibleTiles = true }) + if not pathTo then + pathTo = context.findPath(context.player:getPosition(), {x=mpos.x, y=mpos.y, z=mpos.z}, 10, { ignoreNonPathable = true, precision=1, allowOnlyVisibleTiles = true }) + if not pathTo then + return -1 + end + end + local distance = #pathTo + + if monster == g_game.getAttackingCreature() then + priority = priority + 10 + end + + if distance <= 4 then + priority = priority + 10 + end + if distance <= 2 then + priority = priority + 10 + end + if distance <= 1 then + priority = priority + 10 + end + + if hp <= 25 and config.chase then + priority = priority + 30 + end + + if hp <= 10 then + priority = priority + 10 + end + if hp <= 25 then + priority = priority + 10 + end + if hp <= 50 then + priority = priority + 10 + end + if hp <= 75 then + priority = priority + 10 + end + + priority = priority + config.priority * 10 + return priority + end + + local calculateMonsterDanger = function(monster) + local danger = 0 + local config = getMonsterConfig(monster) + if not config or type(config.danger) ~= 'number' then + return danger + end + danger = danger + config.danger + return danger + end + + local lastAttack = context.now + local lootContainers = {} + local lootTries = 0 + local openContainerRequest = 0 + local waitForLooting = 0 + local lastAttackSpell = 0 + local lastAttackRune = 0 + + local goForLoot = function() + if #lootContainers == 0 or not context.storage.looting.enabled then + return false + end + if modules.game_walking.lastManualWalk + 500 > context.now then + return true + end + + local pos = context.player:getPosition() + table.sort(lootContainers, function(pos1, pos2) + local dist1 = math.max(math.abs(pos.x-pos1.x), math.abs(pos.y-pos1.y)) + local dist2 = math.max(math.abs(pos.x-pos2.x), math.abs(pos.y-pos2.y)) + return dist1 < dist2 + end) + + local cpos = lootContainers[1] + if cpos.z ~= pos.z then + table.remove(lootContainers, 1) + return true + end + + if lootTries >= 5 then + lootTries = 0 + table.remove(lootContainers, 1) + return true + end + local dist = math.max(math.abs(pos.x-cpos.x), math.abs(pos.y-cpos.y)) + if dist <= 5 then + local tile = g_map.getTile(cpos) + if not tile then + table.remove(lootContainers, 1) + return true + end + + local topItem = tile:getTopUseThing() + if not topItem or not topItem:isContainer() then + table.remove(lootContainers, 1) + return true + end + topItem:setMarked('orange') + + if dist <= 1 then + lootTries = lootTries + 1 + openContainerRequest = context.now + g_game.open(topItem) + waitForLooting = math.max(waitForLooting, context.now + 500) + return true + end + end + + if dist <= 25 then + if context.player:isWalking() then + return true + end + + lootTries = lootTries + 1 + if context.autoWalk(cpos, 20, { precision = 1}) then + return true + end + + if context.autoWalk(cpos, 20, { ignoreNonPathable = true, precision = 1}) then + return true + end + + if context.autoWalk(cpos, 20, { ignoreNonPathable = true, precision = 2}) then + return true + end + + if context.autoWalk(cpos, 20, { ignoreNonPathable = true, ignoreCreatures = true, precision = 2}) then + return true + end + else + table.remove(lootContainers, 1) + return false + end + return true + end + + context.onCreatureDisappear(function(creature) + if not creature:isMonster() then + return + end + local pos = context.player:getPosition() + local tpos = creature:getPosition() + if tpos.z ~= pos.z then + return + end + + local config = getMonsterConfig(creature) + if not config or not config.loot then + return + end + local distance = math.max(math.abs(pos.x-tpos.x), math.abs(pos.y-tpos.y)) + if distance > 6 then + return + end + + local tile = g_map.getTile(tpos) + if not tile then + return + end + + local topItem = tile:getTopUseThing() + if not topItem or not topItem:isContainer() then + return + end + + topItem:setMarked('blue') + table.insert(lootContainers, tpos) + end) + + context.onContainerOpen(function(container, prevContainer) + lootTries = 0 + if not context.storage.attacking.enabled then + return + end + + if openContainerRequest + 500 > context.now and #lootContainers > 0 then + waitForLooting = math.max(waitForLooting, context.now + 1000 + container:getItemsCount() * 100) + table.remove(lootContainers, 1) + end + if prevContainer then + container.autoLooting = prevContainer.autoLooting + else + container.autoLooting = (openContainerRequest + 3000 > context.now) + end + end) + + context.macro(200, function() + if not context.storage.attacking.enabled then + return + end + + local attacking = nil + local following = nil + local attackingCandidate = g_game.getAttackingCreature() + local followingCandidate = g_game.getFollowingCreature() + local spectators = context.getSpectators() + local monsters = {} + local danger = 0 + + for i, spec in ipairs(spectators) do + if attackingCandidate and attackingCandidate:getId() == spec:getId() then + attacking = spec + end + if followingCandidate and followingCandidate:getId() == spec:getId() then + following = spec + end + if spec:isMonster() or (spec:isPlayer() and not spec:isLocalPlayer()) then + danger = danger + calculateMonsterDanger(spec) + spec.attackingPriority = calculatePriority(spec) + table.insert(monsters, spec) + end + end + + if following then + return + end + + if waitForLooting > context.now then + return + end + + if #monsters == 0 or context.isInProtectionZone() then + goForLoot() + return + end + + table.sort(monsters, function(a, b) + return a.attackingPriority > b.attackingPriority + end) + + local target = monsters[1] + if target.attackingPriority < 0 then + return + end + + local pos = context.player:getPosition() + local tpos = target:getPosition() + local config = getMonsterConfig(target) + local offsetX = pos.x - tpos.x + local offsetY = pos.y - tpos.y + + local justStartedAttack = false + if target ~= attacking then + g_game.attack(target) + attacking = target + lastAttack = context.now + justStartedAttack = true + end + + -- proceed attack + if not target:isPlayer() and lastAttack + 15000 < context.now then + -- stop and attack again, just in case + g_game.cancelAttack() + g_game.attack(target) + lastAttack = context.now + return + end + + if not justStartedAttack and config.attackSpell and config.attackSpell:len() > 0 then + if context.now > lastAttackSpell + 1000 and context.player:getHealthPercent() > 30 then + if context.saySpell(config.attackSpell, 1500) then + lastAttackRune = context.now + end + end + end + + if not justStartedAttack and config.attackItem and config.attackItem >= 100 then + if context.now > lastAttackRune + 1000 and context.player:getHealthPercent() > 30 then + if context.useRune(config.attackItem, target, 1500) then + lastAttackRune = context.now + end + end + end + + if modules.game_walking.lastManualWalk + 500 > context.now then + return + end + + if danger < 8 then + -- low danger, go for loot first + if goForLoot() then + return + end + end + + target.ignoreByWaypoints = config.dontWalk + if config.dontWalk then + if goForLoot() then + return + end + return + end + + local distance = math.max(math.abs(offsetX), math.abs(offsetY)) + if config.keepDistance then + local minDistance = config.distance + if target:getHealthPercent() <= 25 and config.chase and danger < 10 then + minDistance = 1 + end + if (distance == minDistance or distance == minDistance + 1) then + return + else + local bestDist = 10 + local bestPos = pos + if not context.autoWalk(tpos, 10, { minMargin=minDistance, maxMargin=minDistance + 1, allowOnlyVisibleTiles = true}) then + if not context.autoWalk(tpos, 10, { ignoreNonPathable = true, minMargin=minDistance, maxMargin=minDistance + 1, allowOnlyVisibleTiles = true}) then + if not context.autoWalk(tpos, 10, { ignoreNonPathable = true, ignoreCreatures = true, minMargin=minDistance, maxMargin=minDistance + 2, allowOnlyVisibleTiles = true}) then + return + end + end + end + if not target:isPlayer() then + context.delay(300) + end + end + return + end + + if config.avoidAttacks and distance <= 1 then + if (offsetX == 0 and offsetY ~= 0) then + if context.player:canWalk(Directions.East) then + g_game.walk(Directions.East) + elseif context.player:canWalk(Directions.West) then + g_game.walk(Directions.West) + end + elseif (offsetX ~= 0 and offsetY == 0) then + if context.player:canWalk(Directions.North) then + g_game.walk(Directions.North) + elseif context.player:canWalk(Directions.South) then + g_game.walk(Directions.South) + end + end + end + + if distance > 1 then + if not context.autoWalk(tpos, 10, { precision = 1, allowOnlyVisibleTiles = true}) then + if not context.autoWalk(tpos, 10, { ignoreNonPathable = true, precision = 1, allowOnlyVisibleTiles = true}) then + if not context.autoWalk(tpos, 10, { ignoreNonPathable = true, precision = 2, allowOnlyVisibleTiles = true}) then + return + end + end + end + if not target:isPlayer() then + context.delay(300) + end + end + end) +end + diff --git a/800OTClient/modules/game_bot/panels/basic.lua b/800OTClient/modules/game_bot/panels/basic.lua new file mode 100644 index 0000000..dfa7d87 --- /dev/null +++ b/800OTClient/modules/game_bot/panels/basic.lua @@ -0,0 +1,57 @@ +local context = G.botContext +local Panels = context.Panels + +Panels.Turning = function(parent) + context.macro(1000, "Turning / AntiIdle", nil, function() + context.turn(math.random(1, 4)) + end, parent) +end +Panels.AntiIdle = Panels.Turning + +Panels.AttackSpell = function(parent) + context.macro(500, "Auto attack spell", nil, function() + local target = g_game.getAttackingCreature() + if target and context.getCreatureById(target:getId()) and context.storage.autoAttackText:len() > 0 then + if context.saySpell(context.storage.autoAttackText, 1000) then + context.delay(1000) + end + end + end, parent) + context.addTextEdit("autoAttackText", context.storage.autoAttackText or "exori vis", function(widget, text) + context.storage.autoAttackText = text + end, parent) +end + +Panels.AttackItem = function(parent) + if not parent then + parent = context.panel + end + + local panelName = "attackItem" + local ui = g_ui.createWidget("ItemAndButtonPanel", parent) + ui:setId(panelName) + + ui.title:setText("Auto attack item") + + if not context.storage.attackItem then + context.storage.attackItem = {} + end + + ui.title:setOn(context.storage.attackItem.enabled) + ui.title.onClick = function(widget) + context.storage.attackItem.enabled = not context.storage.attackItem.enabled + widget:setOn(context.storage.attackItem.enabled) + end + + ui.item.onItemChange = function(widget) + context.storage.attackItem.item = widget:getItemId() + end + ui.item:setItemId(context.storage.attackItem.item or 3155) + + context.macro(500, function() + local target = g_game.getAttackingCreature() + if context.storage.attackItem.enabled and target and context.getCreatureById(target:getId()) and context.storage.attackItem.item and context.storage.attackItem.item >= 100 then + context.useWith(context.storage.attackItem.item, target) + end + end) +end diff --git a/800OTClient/modules/game_bot/panels/healing.lua b/800OTClient/modules/game_bot/panels/healing.lua new file mode 100644 index 0000000..fe4171d --- /dev/null +++ b/800OTClient/modules/game_bot/panels/healing.lua @@ -0,0 +1,346 @@ +local context = G.botContext +local Panels = context.Panels + +Panels.Haste = function(parent) + context.macro(500, "Auto Haste", nil, function() + if not context.hasHaste() and context.storage.autoHasteText:len() > 0 then + if context.saySpell(context.storage.autoHasteText, 2500) then + context.delay(5000) + end + end + end, parent) + context.addTextEdit("autoHasteText", context.storage.autoHasteText or "utani hur", function(widget, text) + context.storage.autoHasteText = text + end, parent) +end + +Panels.ManaShield = function(parent) + local lastManaShield = 0 + context.macro(100, "Auto Mana Shield", nil, function() + if not context.hasManaShield() or context.now > lastManaShield + 90000 then + if context.saySpell("utamo vita", 200) then + lastManaShield = context.now + end + end + end, parent) +end + +Panels.AntiParalyze = function(parent) + context.macro(100, "Anti Paralyze", nil, function() + if context.isParalyzed() and context.storage.autoAntiParalyzeText:len() > 0 then + context.saySpell(context.storage.autoAntiParalyzeText, 750) + end + end, parent) + context.addTextEdit("autoAntiParalyzeText", context.storage.autoAntiParalyzeText or "utani hur", function(widget, text) + context.storage.autoAntiParalyzeText = text + end, parent) +end + + +Panels.Health = function(parent) + if not parent then + parent = context.panel + end + + local panelName = "autoHealthPanel" + local panelId = 1 + while parent:getChildById(panelName .. panelId) do + panelId = panelId + 1 + end + panelName = panelName .. panelId + + local ui = g_ui.createWidget("DualScrollPanel", parent) + ui:setId(panelName) + + if not context.storage[panelName] then + context.storage[panelName] = { + item = 266, + min = 20, + max = 80, + text = "exura" + } + end + + ui.title:setOn(context.storage[panelName].enabled) + ui.title.onClick = function(widget) + context.storage[panelName].enabled = not context.storage[panelName].enabled + widget:setOn(context.storage[panelName].enabled) + end + + ui.text.onTextChange = function(widget, text) + context.storage[panelName].text = text + end + ui.text:setText(context.storage[panelName].text or "exura") + + local updateText = function() + ui.title:setText("" .. context.storage[panelName].min .. "% <= hp <= " .. context.storage[panelName].max .. "%") + end + + ui.scroll1.onValueChange = function(scroll, value) + context.storage[panelName].min = value + updateText() + end + ui.scroll2.onValueChange = function(scroll, value) + context.storage[panelName].max = value + updateText() + end + + ui.scroll1:setValue(context.storage[panelName].min) + ui.scroll2:setValue(context.storage[panelName].max) + + context.macro(25, function() + if context.storage[panelName].enabled and context.storage[panelName].text:len() > 0 and context.storage[panelName].min <= context.hppercent() and context.hppercent() <= context.storage[panelName].max then + if context.saySpell(context.storage[panelName].text, 500) then + context.delay(200) + end + end + end) +end + +Panels.HealthItem = function(parent) + if not parent then + parent = context.panel + end + + local panelName = "autoHealthItemPanel" + local panelId = 1 + while parent:getChildById(panelName .. panelId) do + panelId = panelId + 1 + end + panelName = panelName .. panelId + + local ui = g_ui.createWidget("DualScrollItemPanel", parent) + ui:setId(panelName) + + if not context.storage[panelName] then + context.storage[panelName] = { + item = 266, + min = 0, + max = 60 + } + end + + ui.title:setOn(context.storage[panelName].enabled) + ui.title.onClick = function(widget) + context.storage[panelName].enabled = not context.storage[panelName].enabled + widget:setOn(context.storage[panelName].enabled) + end + + ui.item.onItemChange = function(widget) + context.storage[panelName].item = widget:getItemId() + end + ui.item:setItemId(context.storage[panelName].item) + + local updateText = function() + ui.title:setText("" .. (context.storage[panelName].min or "") .. "% <= hp <= " .. (context.storage[panelName].max or "") .. "%") + end + + ui.scroll1.onValueChange = function(scroll, value) + context.storage[panelName].min = value + updateText() + end + ui.scroll2.onValueChange = function(scroll, value) + context.storage[panelName].max = value + updateText() + end + + ui.scroll1:setValue(context.storage[panelName].min) + ui.scroll2:setValue(context.storage[panelName].max) + + context.macro(25, function() + if context.storage[panelName].enabled and context.storage[panelName].item >= 100 and context.storage[panelName].min <= context.hppercent() and context.hppercent() <= context.storage[panelName].max then + if context.useRune(context.storage[panelName].item, context.player, 500) then + context.delay(300) + end + end + end) +end + +Panels.Mana = function(parent) + if not parent then + parent = context.panel + end + + local panelName = "autoManaItemPanel" + local panelId = 1 + while parent:getChildById(panelName .. panelId) do + panelId = panelId + 1 + end + panelName = panelName .. panelId + + local ui = g_ui.createWidget("DualScrollItemPanel", parent) + ui:setId(panelName) + + if not context.storage[panelName] then + context.storage[panelName] = { + item = 268, + min = 0, + max = 60 + } + end + + ui.title:setOn(context.storage[panelName].enabled) + ui.title.onClick = function(widget) + context.storage[panelName].enabled = not context.storage[panelName].enabled + widget:setOn(context.storage[panelName].enabled) + end + + ui.item.onItemChange = function(widget) + context.storage[panelName].item = widget:getItemId() + end + ui.item:setItemId(context.storage[panelName].item) + + local updateText = function() + ui.title:setText("" .. (context.storage[panelName].min or "") .. "% <= mana <= " .. (context.storage[panelName].max or "") .. "%") + end + + ui.scroll1.onValueChange = function(scroll, value) + context.storage[panelName].min = value + updateText() + end + ui.scroll2.onValueChange = function(scroll, value) + context.storage[panelName].max = value + updateText() + end + + ui.scroll1:setValue(context.storage[panelName].min) + ui.scroll2:setValue(context.storage[panelName].max) + + context.macro(25, function() + if context.storage[panelName].enabled and context.storage[panelName].item >= 100 and context.storage[panelName].min <= context.manapercent() and context.manapercent() <= context.storage[panelName].max then + if context.useRune(context.storage[panelName].item, context.player, 500) then + context.delay(300) + end + end + end) +end +Panels.ManaItem = Panels.Mana + +Panels.Equip = function(parent) + if not parent then + parent = context.panel + end + + local panelName = "autoEquipItem" + local panelId = 1 + while parent:getChildById(panelName .. panelId) do + panelId = panelId + 1 + end + panelName = panelName .. panelId + + local ui = g_ui.createWidget("TwoItemsAndSlotPanel", parent) + ui:setId(panelName) + + if not context.storage[panelName] then + context.storage[panelName] = {} + if panelId == 1 then + context.storage[panelName].item1 = 3052 + context.storage[panelName].item2 = 3089 + context.storage[panelName].slot = 9 + end + end + + ui.title:setText("Auto equip") + ui.title:setOn(context.storage[panelName].enabled) + ui.title.onClick = function(widget) + context.storage[panelName].enabled = not context.storage[panelName].enabled + widget:setOn(context.storage[panelName].enabled) + end + + ui.item1:setItemId(context.storage[panelName].item1 or 0) + ui.item1.onItemChange = function(widget) + context.storage[panelName].item1 = widget:getItemId() + end + + ui.item2:setItemId(context.storage[panelName].item2 or 0) + ui.item2.onItemChange = function(widget) + context.storage[panelName].item2 = widget:getItemId() + end + + if not context.storage[panelName].slot then + context.storage[panelName].slot = 1 + end + ui.slot:setCurrentIndex(context.storage[panelName].slot) + ui.slot.onOptionChange = function(widget) + context.storage[panelName].slot = widget.currentIndex + end + + context.macro(250, function() + if context.storage[panelName].enabled and context.storage[panelName].slot > 0 then + local item1 = context.storage[panelName].item1 or 0 + local item2 = context.storage[panelName].item2 or 0 + if item1 < 100 and item2 < 100 then + return + end + local slotItem = context.getSlot(context.storage[panelName].slot) + if slotItem and (slotItem:getId() == item1 or slotItem:getId() == item2) then + return + end + local newItem = context.findItem(context.storage[panelName].item1) + if not newItem then + newItem = context.findItem(context.storage[panelName].item2) + if not newItem then + return + end + end + g_game.move(newItem, {x=65535, y=context.storage[panelName].slot, z=0}) + context.delay(1000) + end + end) +end +Panels.AutoEquip = Panels.Equip + +Panels.Eating = function(parent) + if not parent then + parent = context.panel + end + + local panelName = "autoEatingPanel" + local panelId = 1 + while parent:getChildById(panelName .. panelId) do + panelId = panelId + 1 + end + panelName = panelName .. panelId + + local ui = g_ui.createWidget("ItemsPanel", parent) + ui:setId(panelName) + + if not context.storage[panelName] then + context.storage[panelName] = {} + end + + ui.title:setText("Auto eating") + ui.title:setOn(context.storage[panelName].enabled) + ui.title.onClick = function(widget) + context.storage[panelName].enabled = not context.storage[panelName].enabled + widget:setOn(context.storage[panelName].enabled) + end + + if type(context.storage[panelName].items) ~= 'table' then + context.storage[panelName].items = {3725, 0, 0, 0, 0} + end + + for i=1,5 do + ui.items:getChildByIndex(i).onItemChange = function(widget) + context.storage[panelName].items[i] = widget:getItemId() + end + ui.items:getChildByIndex(i):setItemId(context.storage[panelName].items[i]) + end + + context.macro(15000, function() + if not context.storage[panelName].enabled then + return + end + local candidates = {} + for i, item in pairs(context.storage[panelName].items) do + if item >= 100 then + table.insert(candidates, item) + end + end + if #candidates == 0 then + return + end + context.use(candidates[math.random(1, #candidates)]) + end) +end + diff --git a/800OTClient/modules/game_bot/panels/looting.lua b/800OTClient/modules/game_bot/panels/looting.lua new file mode 100644 index 0000000..cf2785a --- /dev/null +++ b/800OTClient/modules/game_bot/panels/looting.lua @@ -0,0 +1,431 @@ +local context = G.botContext +local Panels = context.Panels + +Panels.Looting = function(parent) + local ui = context.setupUI([[ +Panel + id: looting + height: 180 + + BotLabel + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text: Looting + + ComboBox + id: config + anchors.top: prev.bottom + anchors.left: parent.left + margin-top: 5 + text-offset: 3 0 + width: 130 + + Button + id: enableButton + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 5 + + Button + margin-top: 1 + id: add + anchors.top: prev.bottom + anchors.left: parent.left + text: Add + width: 60 + height: 17 + + Button + id: edit + anchors.top: prev.top + anchors.horizontalCenter: parent.horizontalCenter + text: Edit + width: 60 + height: 17 + + Button + id: remove + anchors.top: prev.top + anchors.right: parent.right + text: Remove + width: 60 + height: 17 + + ScrollablePanel + id: items + anchors.top: prev.bottom + anchors.right: parent.right + anchors.left: parent.left + vertical-scrollbar: scrollBar + margin-right: 5 + margin-top: 2 + height: 70 + layout: + type: grid + cell-size: 34 34 + flow: true + + BotSmallScrollBar + id: scrollBar + anchors.top: prev.top + anchors.bottom: prev.bottom + anchors.right: parent.right + step: 10 + pixels-scroll: true + + BotLabel + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 4 + text: Loot Containers + + ItemsRow + id: containers + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 33 + margin-top: 2 + +]], parent) + + local lootContainers = { ui.containers.item1, ui.containers.item2, ui.containers.item3, ui.containers.item4, ui.containers.item5 } + + if type(context.storage.looting) ~= "table" then + context.storage.looting = {} + end + if type(context.storage.looting.configs) ~= "table" then + context.storage.looting.configs = {} + end + + local getConfigName = function(config) + local matches = regexMatch(config, [[name:\s*([^\n]*)$]]) + if matches[1] and matches[1][2] then + return matches[1][2]:trim() + end + return nil + end + + local items = {} + local itemsByKey = {} + local containers = {} + local commands = {} + local refreshConfig = nil -- declared later + + local createNewConfig = function(focusedWidget) + if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then + return + end + + local tmpItems = {} + local tmpContainers = {} + local focusIndex = 0 + + local newConfig = "" + for i, text in ipairs(commands) do + newConfig = newConfig .. text .. "\n" + end + for i=1,ui.items:getChildCount() do + local widget = ui.items:getChildByIndex(i) + if widget and widget:getItemId() >= 100 then + if tmpItems[widget:getItemId()] == nil then + tmpItems[widget:getItemId()] = 1 + newConfig = newConfig .. "\n" .. widget:getItemId() + end + end + if widget == focusedWidget then + focusIndex = i + end + end + for i, widget in ipairs(lootContainers) do + if widget:getItemId() >= 100 then + if tmpContainers[widget:getItemId()] == nil then + tmpContainers[widget:getItemId()] = 1 -- remove duplicates + newConfig = newConfig .. "\ncontainer:" .. widget:getItemId() + end + end + end + + context.storage.looting.configs[context.storage.looting.activeConfig] = newConfig + refreshConfig(focusIndex) + end + + local parseConfig = function(config) + items = {} + itemsByKey = {} + containers = {} + commands = {} + local matches = regexMatch(config, [[([^:^\n^\s]+)(:?)([^\n]*)]]) + for i=1,#matches do + local command = matches[i][2] + local validation = (matches[i][3] == ":") + local text = matches[i][4] + local commandAsNumber = tonumber(command) + local textAsNumber = tonumber(text) + if commandAsNumber and commandAsNumber >= 100 then + table.insert(items, commandAsNumber) + itemsByKey[commandAsNumber] = 1 + elseif command == "container" and validation and textAsNumber and textAsNumber >= 100 then + containers[textAsNumber] = 1 + elseif validation then + table.insert(commands, command .. ":" .. text) + end + end + + local itemsToShow = #items + 2 + if itemsToShow % 5 ~= 0 then + itemsToShow = itemsToShow + 5 - itemsToShow % 5 + end + if itemsToShow < 10 then + itemsToShow = 10 + end + + for i=1,itemsToShow do + local widget = g_ui.createWidget("BotItem", ui.items) + local itemId = 0 + if i <= #items then + itemId = items[i] + end + widget:setItemId(itemId) + widget.onItemChange = createNewConfig + end + + for i, widget in ipairs(lootContainers) do + widget:setItemId(0) + end + local containerIndex = 1 + for containerId, i in pairs(containers) do + if lootContainers[containerIndex] then + lootContainers[containerIndex]:setItemId(containerId) + end + containerIndex = containerIndex + 1 + end + for i, widget in ipairs(lootContainers) do + widget.onItemChange = createNewConfig + end + end + + local ignoreOnOptionChange = true + refreshConfig = function(focusIndex) + ignoreOnOptionChange = true + if context.storage.looting.enabled then + ui.enableButton:setText("On") + ui.enableButton:setColor('#00AA00FF') + else + ui.enableButton:setText("Off") + ui.enableButton:setColor('#FF0000FF') + end + + ui.config:clear() + for i, config in ipairs(context.storage.looting.configs) do + local name = getConfigName(config) + if not name then + name = "Unnamed config" + end + ui.config:addOption(name) + end + + if (not context.storage.looting.activeConfig or context.storage.looting.activeConfig == 0) and #context.storage.looting.configs > 0 then + context.storage.looting.activeConfig = 1 + end + + ui.items:destroyChildren() + for i, widget in ipairs(lootContainers) do + widget.onItemChange = nil + widget:setItemId(0) + widget:setItemCount(0) + end + + if context.storage.looting.activeConfig and context.storage.looting.configs[context.storage.looting.activeConfig] then + ui.config:setCurrentIndex(context.storage.looting.activeConfig) + parseConfig(context.storage.looting.configs[context.storage.looting.activeConfig]) + end + + context.saveConfig() + if focusIndex and focusIndex > 0 and ui.items:getChildByIndex(focusIndex) then + ui.items:focusChild(ui.items:getChildByIndex(focusIndex)) + end + + ignoreOnOptionChange = false + end + + ui.config.onOptionChange = function(widget) + if not ignoreOnOptionChange then + context.storage.looting.activeConfig = widget.currentIndex + refreshConfig() + end + end + ui.enableButton.onClick = function() + if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then + return + end + context.storage.looting.enabled = not context.storage.looting.enabled + refreshConfig() + end + ui.add.onClick = function() + modules.client_textedit.multilineEditor("Looting editor", "name:Config name", function(newText) + table.insert(context.storage.looting.configs, newText) + context.storage.looting.activeConfig = #context.storage.looting.configs + refreshConfig() + end) + end + ui.edit.onClick = function() + if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then + return + end + modules.client_textedit.multilineEditor("Looting editor", context.storage.looting.configs[context.storage.looting.activeConfig], function(newText) + context.storage.looting.configs[context.storage.looting.activeConfig] = newText + refreshConfig() + end) + end + ui.remove.onClick = function() + if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then + return + end + local questionWindow = nil + local closeWindow = function() + questionWindow:destroy() + end + local removeConfig = function() + closeWindow() + if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then + return + end + context.storage.looting.enabled = false + table.remove(context.storage.looting.configs, context.storage.looting.activeConfig) + context.storage.looting.activeConfig = 0 + refreshConfig() + end + questionWindow = context.displayGeneralBox(tr('Remove config'), tr('Do you want to remove current looting config?'), { + { text=tr('Yes'), callback=removeConfig }, + { text=tr('No'), callback=closeWindow }, + anchor=AnchorHorizontalCenter}, removeConfig, closeWindow) + end + refreshConfig() + + context.onContainerOpen(function(container, prevContainer) + if context.storage.attacking.enabled then + return + end + if prevContainer then + container.autoLooting = prevContainer.autoLooting + else + container.autoLooting = true + end + end) + + context.macro(200, function() + if not context.storage.looting.enabled then + return + end + local candidates = {} + local lootContainersCandidates = {} + for containerId, container in pairs(g_game.getContainers()) do + local containerItem = container:getContainerItem() + if container.autoLooting and container:getItemsCount() > 0 and (not containerItem or containers[containerItem:getId()] == nil) then + table.insert(candidates, container) + elseif containerItem and containers[containerItem:getId()] ~= nil then + table.insert(lootContainersCandidates, container) + end + end + if #lootContainersCandidates == 0 then + for slot = InventorySlotFirst, InventorySlotLast do + local item = context.getInventoryItem(slot) + if item and item:isContainer() and containers[item:getId()] ~= nil then + table.insert(lootContainersCandidates, item) + end + end + if #lootContainersCandidates > 0 then + -- try to open inventory backpack + local target = lootContainersCandidates[math.random(1,#lootContainersCandidates)] + g_game.open(target, nil) + context.delay(200) + end + return + end + + if #candidates == 0 then + return + end + + local container = candidates[math.random(1,#candidates)] + local nextContainers = {} + local foundItem = nil + for i, item in ipairs(container:getItems()) do + if item:isContainer() then + table.insert(nextContainers, item) + elseif itemsByKey[item:getId()] ~= nil then + foundItem = item + break + end + end + + -- found item to loot + if foundItem then + -- find backpack for it, first backpack with same items + for i, container in ipairs(lootContainersCandidates) do + if container:getItemsCount() < container:getCapacity() or foundItem:isStackable() then -- has space + for j, item in ipairs(container:getItems()) do + if item:getId() == foundItem:getId() then + if foundItem:isStackable() then + if item:getCount() ~= 100 then + g_game.move(foundItem, container:getSlotPosition(j - 1), foundItem:getCount()) + return + end + else + g_game.move(foundItem, container:getSlotPosition(container:getItemsCount()), foundItem:getCount()) + return + end + end + end + end + end + -- now any backpack with empty slot + for i, container in ipairs(lootContainersCandidates) do + if container:getItemsCount() < container:getCapacity() then -- has space + g_game.move(foundItem, container:getSlotPosition(container:getItemsCount()), foundItem:getCount()) + return + end + end + + -- can't find backpack, try to open new + for i, container in ipairs(lootContainersCandidates) do + local candidates = {} + for j, item in ipairs(container:getItems()) do + if item:isContainer() and containers[item:getId()] ~= nil then + table.insert(candidates, item) + end + end + if #candidates > 0 then + g_game.open(candidates[math.random(1,#candidates)], container) + return + end + -- full, close it + g_game.close(container) + return + end + return + end + + -- open remaining containers + if #nextContainers == 0 then + return + end + local delay = 1 + for i=2,#nextContainers do + -- if more than 1 container, open them in new window + context.schedule(delay, function() + g_game.open(nextContainers[i], nil) + end) + delay = delay + 250 + end + context.schedule(delay, function() + g_game.open(nextContainers[1], container) + end) + context.delay(150 + delay) + end) +end + diff --git a/800OTClient/modules/game_bot/panels/tools.lua b/800OTClient/modules/game_bot/panels/tools.lua new file mode 100644 index 0000000..1094b6e --- /dev/null +++ b/800OTClient/modules/game_bot/panels/tools.lua @@ -0,0 +1,36 @@ +local context = G.botContext +local Panels = context.Panels + +Panels.TradeMessage = function(parent) + context.macro(60000, "Send message on trade", nil, function() + local trade = context.getChannelId("advertising") + if not trade then + trade = context.getChannelId("trade") + end + if context.storage.autoTradeMessage:len() > 0 and trade then + context.sayChannel(trade, context.storage.autoTradeMessage) + end + end, parent) + context.addTextEdit("autoTradeMessage", context.storage.autoTradeMessage or "I'm using OTClientV8 - https://github.com/OTCv8/otclientv8", function(widget, text) + context.storage.autoTradeMessage = text + end, parent) +end + +Panels.AutoStackItems = function(parent) + context.macro(500, "Auto stacking items", nil, function() + local containers = context.getContainers() + for i, container in pairs(containers) do + local toStack = {} + for j, item in ipairs(container:getItems()) do + if item:isStackable() and item:getCount() ~= 100 then + local otherItem = toStack[item:getId()] + if otherItem then + g_game.move(item, otherItem, item:getCount()) + return + end + toStack[item:getId()] = container:getSlotPosition(j - 1) + end + end + end + end, parent) +end \ No newline at end of file diff --git a/800OTClient/modules/game_bot/panels/war.lua b/800OTClient/modules/game_bot/panels/war.lua new file mode 100644 index 0000000..44cf767 --- /dev/null +++ b/800OTClient/modules/game_bot/panels/war.lua @@ -0,0 +1,127 @@ +local context = G.botContext +local Panels = context.Panels + +Panels.AttackLeaderTarget = function(parent) + local toAttack = nil + context.onMissle(function(missle) + if not context.storage.attackLeader or context.storage.attackLeader:len() == 0 then + return + end + local src = missle:getSource() + if src.z ~= context.posz() then + return + end + local from = g_map.getTile(src) + local to = g_map.getTile(missle:getDestination()) + if not from or not to then + return + end + local fromCreatures = from:getCreatures() + local toCreatures = to:getCreatures() + if #fromCreatures ~= 1 or #toCreatures ~= 1 then + return + end + local c1 = fromCreatures[1] + if c1:getName():lower() == context.storage.attackLeader:lower() then + toAttack = toCreatures[1] + end + end) + context.macro(50, "Attack leader's target", nil, function() + if toAttack and context.storage.attackLeader:len() > 0 and toAttack ~= g_game.getAttackingCreature() then + g_game.attack(toAttack) + toAttack = nil + end + end, parent) + context.addTextEdit("attackLeader", context.storage.attackLeader or "player name", function(widget, text) + context.storage.attackLeader = text + end, parent) +end + + +Panels.LimitFloor = function(parent) + context.onPlayerPositionChange(function(pos) + if context.storage.limitFloor then + local gameMapPanel = modules.game_interface.getMapPanel() + if gameMapPanel then + gameMapPanel:lockVisibleFloor(pos.z) + end + end + end) + + local switch = context.addSwitch("limitFloor", "Don't show higher floors", function(widget) + widget:setOn(not widget:isOn()) + context.storage.limitFloor = widget:isOn() + local gameMapPanel = modules.game_interface.getMapPanel() + if gameMapPanel then + if context.storage.limitFloor then + gameMapPanel:lockVisibleFloor(context.posz()) + else + gameMapPanel:unlockVisibleFloor() + end + end + end, parent) + switch:setOn(context.storage.limitFloor) +end + +Panels.AntiPush = function(parent) + if not parent then + parent = context.panel + end + + local panelName = "antiPushPanel" + local ui = g_ui.createWidget("ItemsPanel", parent) + ui:setId(panelName) + + if not context.storage[panelName] then + context.storage[panelName] = {} + end + + ui.title:setText("Anti push") + ui.title:setOn(context.storage[panelName].enabled) + ui.title.onClick = function(widget) + context.storage[panelName].enabled = not context.storage[panelName].enabled + widget:setOn(context.storage[panelName].enabled) + end + + if type(context.storage[panelName].items) ~= 'table' then + context.storage[panelName].items = {3031, 3035, 0, 0, 0} + end + + for i=1,5 do + ui.items:getChildByIndex(i).onItemChange = function(widget) + context.storage[panelName].items[i] = widget:getItemId() + end + ui.items:getChildByIndex(i):setItemId(context.storage[panelName].items[i]) + end + + context.macro(100, function() + if not context.storage[panelName].enabled then + return + end + local tile = g_map.getTile(context.player:getPosition()) + if not tile then + return + end + local topItem = tile:getTopUseThing() + if topItem and topItem:isStackable() then + topItem = topItem:getId() + else + topItem = 0 + end + local candidates = {} + for i, item in pairs(context.storage[panelName].items) do + if item >= 100 and item ~= topItem and context.findItem(item) then + table.insert(candidates, item) + end + end + if #candidates == 0 then + return + end + if type(context.storage[panelName].lastItem) ~= 'number' or context.storage[panelName].lastItem > #candidates then + context.storage[panelName].lastItem = 1 + end + local item = context.findItem(candidates[context.storage[panelName].lastItem]) + g_game.move(item, context.player:getPosition(), 1) + context.storage[panelName].lastItem = context.storage[panelName].lastItem + 1 + end) +end diff --git a/800OTClient/modules/game_bot/panels/waypoints.lua b/800OTClient/modules/game_bot/panels/waypoints.lua new file mode 100644 index 0000000..55f508f --- /dev/null +++ b/800OTClient/modules/game_bot/panels/waypoints.lua @@ -0,0 +1,754 @@ +local context = G.botContext +local Panels = context.Panels + +Panels.Waypoints = function(parent) + local ui = context.setupUI([[ +Panel + id: waypoints + height: 206 + + BotLabel + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text: Waypoints + + ComboBox + id: config + anchors.top: prev.bottom + anchors.left: parent.left + margin-top: 5 + text-offset: 3 0 + width: 130 + + Button + id: enableButton + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 5 + + Button + margin-top: 1 + id: add + anchors.top: prev.bottom + anchors.left: parent.left + text: Add + width: 60 + height: 17 + + Button + id: edit + anchors.top: prev.top + anchors.horizontalCenter: parent.horizontalCenter + text: Edit + width: 60 + height: 17 + + Button + id: remove + anchors.top: prev.top + anchors.right: parent.right + text: Remove + width: 60 + height: 17 + + TextList + id: list + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + vertical-scrollbar: listScrollbar + margin-right: 15 + margin-top: 2 + height: 60 + focusable: false + auto-focus: first + + VerticalScrollBar + id: listScrollbar + anchors.top: prev.top + anchors.bottom: prev.bottom + anchors.right: parent.right + pixels-scroll: true + step: 5 + + Label + id: pos + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + text-align: center + margin-top: 2 + + Button + id: wGoto + anchors.top: prev.bottom + anchors.left: parent.left + text: Goto + width: 61 + margin-top: 1 + height: 17 + + Button + id: wUse + anchors.top: prev.top + anchors.left: prev.right + text: Use + width: 61 + height: 17 + + Button + id: wUseWith + anchors.top: prev.top + anchors.left: prev.right + text: UseWith + width: 61 + height: 17 + + Button + id: wWait + anchors.top: prev.bottom + anchors.left: parent.left + text: Wait + width: 61 + margin-top: 1 + height: 17 + + Button + id: wSay + anchors.top: prev.top + anchors.left: prev.right + text: Say + width: 61 + height: 17 + + Button + id: wNpc + anchors.top: prev.top + anchors.left: prev.right + text: Say NPC + width: 61 + height: 17 + + Button + id: wLabel + anchors.top: prev.bottom + anchors.left: parent.left + text: Label + width: 61 + margin-top: 1 + height: 17 + + Button + id: wFollow + anchors.top: prev.top + anchors.left: prev.right + text: Follow + width: 61 + height: 17 + + Button + id: wFunction + anchors.top: prev.top + anchors.left: prev.right + text: Function + width: 61 + height: 17 + + BotSwitch + id: recording + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + text: Auto Recording + height: 17 + +]], parent) + + if type(context.storage.cavebot) ~= "table" then + context.storage.cavebot = {} + end + if type(context.storage.cavebot.configs) ~= "table" then + context.storage.cavebot.configs = {} + end + + local getConfigName = function(config) + local matches = regexMatch(config, [[name:\s*([^\n]*)$]]) + if matches[1] and matches[1][2] then + return matches[1][2]:trim() + end + return nil + end + + local isValidCommand = function(command) + if command == "goto" then + return true + elseif command == "use" then + return true + elseif command == "usewith" then + return true + elseif command == "wait" then + return true + elseif command == "say" then + return true + elseif command == "npc" then + return true + elseif command == "follow" then + return true + elseif command == "label" then + return true + elseif command == "gotolabel" then + return true + elseif command == "comment" then + return true + elseif command == "function" then + return true + end + return false + end + + local commands = {} + local waitTo = 0 + local autoRecording = false + + local parseConfig = function(config) + commands = {} + local matches = regexMatch(config, [[([^:^\n^\s]+)(:?)([^\n]*)]]) + for i=1,#matches do + local command = matches[i][2] + local validation = (matches[i][3] == ":") + if not validation or isValidCommand(command) then + local text = matches[i][4] + if validation then + table.insert(commands, {command=command:lower(), text=text}) + elseif #commands > 0 then + commands[#commands].text = commands[#commands].text .. "\n" .. matches[i][1] + end + end + end + + for i=1,#commands do + local label = g_ui.createWidget("CaveBotLabel", ui.list) + label:setText(commands[i].command .. ":" .. commands[i].text) + if commands[i].command == "goto" then + label:setColor("green") + elseif commands[i].command == "label" then + label:setColor("yellow") + elseif commands[i].command == "comment" then + label:setText(commands[i].text) + label:setColor("white") + elseif commands[i].command == "use" or commands[i].command == "usewith" then + label:setColor("orange") + elseif commands[i].command == "gotolabel" then + label:setColor("red") + end + end + end + + local ignoreOnOptionChange = true + local refreshConfig = function(scrollDown) + ignoreOnOptionChange = true + if context.storage.cavebot.enabled then + autoRecording = false + ui.recording:setOn(false) + ui.enableButton:setText("On") + ui.enableButton:setColor('#00AA00FF') + else + ui.enableButton:setText("Off") + ui.enableButton:setColor('#FF0000FF') + ui.recording:setOn(autoRecording) + end + + ui.config:clear() + for i, config in ipairs(context.storage.cavebot.configs) do + local name = getConfigName(config) + if not name then + name = "Unnamed config" + end + ui.config:addOption(name) + end + + if (not context.storage.cavebot.activeConfig or context.storage.cavebot.activeConfig == 0) and #context.storage.cavebot.configs > 0 then + context.storage.cavebot.activeConfig = 1 + end + + ui.list:destroyChildren() + + if context.storage.cavebot.activeConfig and context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + ui.config:setCurrentIndex(context.storage.cavebot.activeConfig) + parseConfig(context.storage.cavebot.configs[context.storage.cavebot.activeConfig]) + end + + context.saveConfig() + if scrollDown and ui.list:getLastChild() then + ui.list:focusChild(ui.list:getLastChild()) + end + + waitTo = 0 + ignoreOnOptionChange = false + end + + + ui.config.onOptionChange = function(widget) + if not ignoreOnOptionChange then + context.storage.cavebot.activeConfig = widget.currentIndex + refreshConfig() + end + end + ui.enableButton.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + context.storage.cavebot.enabled = not context.storage.cavebot.enabled + if autoRecording then + refreshConfig() + elseif context.storage.cavebot.enabled then + ui.enableButton:setText("On") + ui.enableButton:setColor('#00AA00FF') + else + ui.enableButton:setText("Off") + ui.enableButton:setColor('#FF0000FF') + end + end + ui.add.onClick = function() + modules.client_textedit.multilineEditor("Waypoints editor", "name:Config name\nlabel:start\n", function(newText) + table.insert(context.storage.cavebot.configs, newText) + context.storage.cavebot.activeConfig = #context.storage.cavebot.configs + refreshConfig() + end) + end + ui.edit.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + modules.client_textedit.multilineEditor("Waypoints editor", context.storage.cavebot.configs[context.storage.cavebot.activeConfig], function(newText) + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = newText + refreshConfig() + end) + end + ui.remove.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + local questionWindow = nil + local closeWindow = function() + questionWindow:destroy() + end + local removeConfig = function() + closeWindow() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + context.storage.cavebot.enabled = false + table.remove(context.storage.cavebot.configs, context.storage.cavebot.activeConfig) + context.storage.cavebot.activeConfig = 0 + refreshConfig() + end + questionWindow = context.displayGeneralBox(tr('Remove config'), tr('Do you want to remove current waypoints config?'), { + { text=tr('Yes'), callback=removeConfig }, + { text=tr('No'), callback=closeWindow }, + anchor=AnchorHorizontalCenter}, removeConfig, closeWindow) + end + + -- waypoint editor + -- auto recording + local stepsSincleLastPos = 0 + + context.onPlayerPositionChange(function(newPos, oldPos) + ui.pos:setText("Position: " .. newPos.x .. ", " .. newPos.y .. ", " .. newPos.z) + if not autoRecording then + return + end + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + local newText = "" + if newPos.z ~= oldPos.z then + newText = "goto:" .. oldPos.x .. "," .. oldPos.y .. "," .. oldPos.z + newText = newText .. "\ngoto:" .. newPos.x .. "," .. newPos.y .. "," .. newPos.z + stepsSincleLastPos = 0 + else + stepsSincleLastPos = stepsSincleLastPos + 1 + if stepsSincleLastPos > 10 then + newText = "goto:" .. oldPos.x .. "," .. oldPos.y .. "," .. oldPos.z + stepsSincleLastPos = 0 + end + end + + if newText:len() > 0 then + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\n" .. newText + refreshConfig(true) + end + end) + + context.onUse(function(pos, itemId, stackPos, subType) + if not autoRecording then + return + end + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + if pos.x == 0xFFFF then + return + end + stepsSincleLastPos = 0 + local playerPos = context.player:getPosition() + newText = "goto:" .. playerPos.x .. "," .. playerPos.y .. "," .. playerPos.z .. "\nuse:" .. pos.x .. "," .. pos.y .. "," .. pos.z + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\n" .. newText + refreshConfig(true) + end) + context.onUseWith(function(pos, itemId, target, subType) + if not autoRecording then + return + end + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + if not target:isItem() then + return + end + local targetPos = target:getPosition() + if targetPos.x == 0xFFFF then + return + end + stepsSincleLastPos = 0 + local playerPos = context.player:getPosition() + newText = "goto:" .. playerPos.x .. "," .. playerPos.y .. "," .. playerPos.z .. "\nusewith:" .. itemId .. "," .. targetPos.x .. "," .. targetPos.y .. "," .. targetPos.z + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\n" .. newText + refreshConfig(true) + end) + + -- ui + local pos = context.player:getPosition() + ui.pos:setText("Position: " .. pos.x .. ", " .. pos.y .. ", " .. pos.z) + + ui.wGoto.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + local pos = context.player:getPosition() + modules.client_textedit.singlelineEditor("" .. pos.x .. "," .. pos.y .. "," .. pos.z, function(newText) + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\ngoto:" .. newText + refreshConfig(true) + end) + end + + ui.wUse.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + local pos = context.player:getPosition() + modules.client_textedit.singlelineEditor("" .. pos.x .. "," .. pos.y .. "," .. pos.z, function(newText) + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nuse:" .. newText + refreshConfig(true) + end) + end + + ui.wUseWith.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + local pos = context.player:getPosition() + modules.client_textedit.singlelineEditor("ITEMID," .. pos.x .. "," .. pos.y .. "," .. pos.z, function(newText) + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nusewith:" .. newText + refreshConfig(true) + end) + end + + ui.wWait.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + modules.client_textedit.singlelineEditor("1000", function(newText) + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nwait:" .. newText + refreshConfig(true) + end) + end + + ui.wSay.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + modules.client_textedit.singlelineEditor("text", function(newText) + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nsay:" .. newText + refreshConfig(true) + end) + end + + ui.wNpc.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + modules.client_textedit.singlelineEditor("text", function(newText) + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nnpc:" .. newText + refreshConfig(true) + end) + end + + ui.wLabel.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + modules.client_textedit.singlelineEditor("label name", function(newText) + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nlabel:" .. newText + refreshConfig(true) + end) + end + + ui.wFollow.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + modules.client_textedit.singlelineEditor("creature name", function(newText) + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nfollow:" .. newText + refreshConfig(true) + end) + end + + ui.wFunction.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + modules.client_textedit.multilineEditor("Add function", "function(waypoints)\n -- your lua code, function is executed if previous goto was successful or is just after label\n\n -- must return true to execute next command, otherwise will run in loop till correct return\n return true\nend", function(newText) + context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nfunction:" .. newText + refreshConfig(true) + end) + end + + ui.recording.onClick = function() + if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then + return + end + autoRecording = not autoRecording + if autoRecording then + context.storage.cavebot.enabled = false + stepsSincleLastPos = 10 + end + refreshConfig(true) + end + + refreshConfig() + + local usedGotoLabel = false + local executeNextMacroCall = false + local commandExecutionNo = 0 + local lastGotoSuccesful = true + local lastOpenedContainer = 0 + + local functions = { + enable = function() + context.storage.cavebot.enabled = true + refreshConfig() + end, + disable = function() + context.storage.cavebot.enabled = false + refreshConfig() + end, + refresh = function() + refreshConfig() + end, + wait = function(peroid) + waitTo = context.now + peroid + end, + waitTo = function(timepoint) + waitTo = timepoint + end, + gotoLabel = function(name) + for i=1,ui.list:getChildCount() do + local command = commands[i] + if command and command.command == "label" and command.text == name then + ui.list:focusChild(ui.list:getChildByIndex(i)) + usedGotoLabel = true + lastGotoSuccesful = true + return true + end + end + end + } + + context.onContainerOpen(function(container) + if container:getItemsCount() > 0 then + lastOpenedContainer = context.now + container:getItemsCount() * 100 + end + end) + + + context.macro(250, function() + if not context.storage.cavebot.enabled then + return + end + + if modules.game_walking.lastManualWalk + 500 > context.now then + return + end + + -- wait if walked or opened container recently + if context.player:isWalking() or lastOpenedContainer + 1000 > context.now then + executeNextMacroCall = false + return + end + + -- wait if attacking/following creature + local attacking = g_game.getAttackingCreature() + local following = g_game.getFollowingCreature() + if (attacking and context.getCreatureById(attacking:getId()) and not attacking.ignoreByWaypoints) or (following and context.getCreatureById(following:getId())) then + executeNextMacroCall = false + return + end + + if not executeNextMacroCall then + executeNextMacroCall = true + return + end + executeNextMacroCall = false + + local commandWidget = ui.list:getFocusedChild() + if not commandWidget then + if ui.list:getFirstChild() then + ui.list:focusChild(ui.list:getFirstChild()) + end + return + end + + local commandIndex = ui.list:getChildIndex(commandWidget) + local command = commands[commandIndex] + if not command then + if ui.list:getFirstChild() then + ui.list:focusChild(ui.list:getFirstChild()) + end + return + end + + if commandIndex == 1 then + lastGotoSuccesful = true + end + + if command.command == "goto" or command.command == "follow" then + local matches = regexMatch(command.text, [[([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)]]) + if (#matches == 1 and #matches[1] == 4) or command.command == "follow" then + local position = nil + if command.command == "follow" then + local creature = context.getCreatureByName(command.text) + if creature then + position = creature:getPosition() + end + else + position = {x=tonumber(matches[1][2]), y=tonumber(matches[1][3]), z=tonumber(matches[1][4])} + end + local distance = 0 + if position then + distance = context.getDistanceBetween(position, context.player:getPosition()) + end + if distance > 100 or not position or position.z ~= context.player:getPosition().z then + lastGotoSuccesful = false + elseif distance > 0 then + if not context.findPath(context.player:getPosition(), position, 100, { ignoreNonPathable = true, precision = 1, ignoreCreatures = true }) then + lastGotoSuccesful = false + executeNextMacroCall = true + else + commandExecutionNo = commandExecutionNo + 1 + lastGotoSuccesful = false + if commandExecutionNo <= 3 then -- try max 3 times + if not context.autoWalk(position, distance * 2, { ignoreNonPathable = false }) then + if commandExecutionNo > 1 then + if context.autoWalk(position, distance * 2, { ignoreNonPathable = true, precision = 1 }) then + context.delay(500) + end + end + return + end + return + elseif commandExecutionNo == 4 then -- try last time, location close to destination + if context.autoWalk(position, distance * 2, { ignoreNonPathable = true, ignoreLastCreature = true, precision = 2, allowUnseen = true }) then + context.delay(500) + return + end + elseif distance <= 2 then + lastGotoSuccesful = true + executeNextMacroCall = true + end + end + else + lastGotoSuccesful = true + executeNextMacroCall = true + end + else + context.error("Waypoints: invalid use of goto function") + end + elseif command.command == "use" then + local matches = regexMatch(command.text, [[([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)]]) + if #matches == 1 and #matches[1] == 4 then + local position = {x=tonumber(matches[1][2]), y=tonumber(matches[1][3]), z=tonumber(matches[1][4])} + if context.player:getPosition().z == position.z then + local tile = g_map.getTile(position) + if tile then + local topThing = tile:getTopUseThing() + if topThing then + g_game.use(topThing) + context.delay(500) + end + end + end + else + context.error("Waypoints: invalid use of use function") + end + elseif command.command == "usewith" then + local matches = regexMatch(command.text, [[([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)]]) + if #matches == 1 and #matches[1] == 5 then + local itemId = tonumber(matches[1][2]) + local position = {x=tonumber(matches[1][3]), y=tonumber(matches[1][4]), z=tonumber(matches[1][5])} + if context.player:getPosition().z == position.z then + local tile = g_map.getTile(position) + if tile then + local topThing = tile:getTopUseThing() + if topThing then + context.useWith(itemId, topThing) + context.delay(500) + end + end + end + else + context.error("Waypoints: invalid use of usewith function") + end + elseif command.command == "wait" and lastGotoSuccesful then + if not waitTo or waitTo == 0 then + waitTo = context.now + tonumber(command.text) + end + if context.now < waitTo then + return + end + waitTo = 0 + elseif command.command == "say" and lastGotoSuccesful then + context.say(command.text) + elseif command.command == "npc" and lastGotoSuccesful then + context.sayNpc(command.text) + elseif command.command == "function" and lastGotoSuccesful then + usedGotoLabel = false + local status, result = pcall(function() + return assert(load("return " .. command.text, nil, nil, context))()(functions) + end) + if not status then + context.error("Waypoints function execution error:\n" .. result) + context.delay(2500) + end + if not result or usedGotoLabel then + return + end + elseif command.command == "gotolabel" then + if functions.gotoLabel(command.text) then + return + end + end + + local nextIndex = 1 + commandIndex % #commands + local nextChild = ui.list:getChildByIndex(nextIndex) + if nextChild then + ui.list:focusChild(nextChild) + commandExecutionNo = 0 + end + end) + + return functions +end + diff --git a/800OTClient/modules/game_bot/scripts.png b/800OTClient/modules/game_bot/scripts.png new file mode 100644 index 0000000..640c0e9 Binary files /dev/null and b/800OTClient/modules/game_bot/scripts.png differ diff --git a/800OTClient/modules/game_bot/ui/basic.otui b/800OTClient/modules/game_bot/ui/basic.otui new file mode 100644 index 0000000..56d9e03 --- /dev/null +++ b/800OTClient/modules/game_bot/ui/basic.otui @@ -0,0 +1,88 @@ +BotButton < Button + height: 17 + margin-top: 2 + +BotSwitch < Button + margin-top: 2 + height: 17 + image-color: green + $!on: + image-color: red + +SmallBotSwitch < Button + margin-top: 2 + height: 15 + image-color: green + $!on: + image-color: red + +BotLabel < Label + margin-top: 2 + height: 15 + text-auto-resize: true + text-align: center + text-wrap: true + +BotItem < Item + virtual: true + &selectable: true + &editable: true + +BotTextEdit < TextEdit + @onClick: modules.client_textedit.show(self) + text-align: center + multiline: false + focusable: false + height: 20 + +BotSeparator < HorizontalSeparator + margin-top: 5 + margin-bottom: 3 + +BotSmallScrollBar < SmallScrollBar + +BotPanel < Panel + margin-top: 1 + ScrollablePanel + id: content + anchors.fill: parent + margin-right: 8 + margin-left: 1 + margin-bottom: 5 + vertical-scrollbar: botPanelScroll + layout: + type: verticalBox + $mobile: + margin-right: 16 + + BotSmallScrollBar + id: botPanelScroll + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + +CaveBotLabel < Label + background-color: alpha + text-offset: 2 0 + focusable: true + + $focus: + background-color: #00000055 + +SlotComboBoxPopupMenu < ComboBoxPopupMenu +SlotComboBoxPopupMenuButton < ComboBoxPopupMenuButton +SlotComboBox < ComboBox + @onSetup: | + self:addOption("Head") + self:addOption("Neck") + self:addOption("Back") + self:addOption("Body") + self:addOption("Right") + self:addOption("Left") + self:addOption("Leg") + self:addOption("Feet") + self:addOption("Finger") + self:addOption("Ammo") + self:addOption("Purse") + + diff --git a/800OTClient/modules/game_bot/ui/config.otui b/800OTClient/modules/game_bot/ui/config.otui new file mode 100644 index 0000000..c99d741 --- /dev/null +++ b/800OTClient/modules/game_bot/ui/config.otui @@ -0,0 +1,54 @@ +BotConfig < Panel + id: botConfig + height: 45 + margin-left: 2 + margin-right: 2 + + ComboBox + id: list + anchors.top: parent.top + anchors.left: parent.left + text-offset: 3 0 + width: 130 + + Button + id: switch + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 5 + $on: + text: On + color: #00AA00 + + $!on: + text: Off + color: #FF0000 + + Button + margin-top: 2 + id: add + anchors.top: prev.bottom + anchors.left: parent.left + text: Add + width: 56 + height: 18 + text-offet: 0 2 + + Button + id: edit + anchors.top: prev.top + anchors.horizontalCenter: parent.horizontalCenter + text: Edit + width: 56 + height: 18 + text-offet: 0 2 + + Button + id: remove + anchors.top: prev.top + anchors.right: parent.right + text: Remove + width: 56 + height: 18 + text-offet: 0 2 \ No newline at end of file diff --git a/800OTClient/modules/game_bot/ui/container.otui b/800OTClient/modules/game_bot/ui/container.otui new file mode 100644 index 0000000..acb04a1 --- /dev/null +++ b/800OTClient/modules/game_bot/ui/container.otui @@ -0,0 +1,19 @@ +BotContainer < Panel + height: 68 + + ScrollablePanel + id: items + anchors.fill: parent + vertical-scrollbar: scroll + layout: + type: grid + cell-size: 34 34 + flow: true + + BotSmallScrollBar + id: scroll + anchors.top: prev.top + anchors.bottom: prev.bottom + anchors.right: parent.right + step: 10 + pixels-scroll: true diff --git a/800OTClient/modules/game_bot/ui/icons.otui b/800OTClient/modules/game_bot/ui/icons.otui new file mode 100644 index 0000000..655c66c --- /dev/null +++ b/800OTClient/modules/game_bot/ui/icons.otui @@ -0,0 +1,60 @@ +BotIcon < UIWidget + size: 50 50 + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + focusable: false + phantom: false + draggable: true + + UIItem + id: item + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 6 + virtual: true + phantom: true + size: 32 32 + + UICreature + id: creature + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 0 + size: 48 48 + phantom: true + + UIWidget + id: status + anchors.top: parent.top + anchors.left: parent.left + size: 18 10 + color: black + font: terminus-10px + phantom: true + + $on: + text: ON + background: green + + $!on: + text: OFF + background: red + + UIWidget + id: hotkey + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + size: 18 10 + color: white + phantom: true + text-align: right + + UIWidget + id: text + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + text-wrap: true + text-auto-resize: true + phantom: true diff --git a/800OTClient/modules/game_bot/ui/panels.otui b/800OTClient/modules/game_bot/ui/panels.otui new file mode 100644 index 0000000..bb0e8df --- /dev/null +++ b/800OTClient/modules/game_bot/ui/panels.otui @@ -0,0 +1,310 @@ +DualScrollPanel < Panel + height: 51 + margin-top: 3 + + SmallBotSwitch + id: title + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-align: center + + HorizontalScrollBar + id: scroll1 + anchors.left: title.left + anchors.right: title.horizontalCenter + anchors.top: title.bottom + margin-right: 2 + margin-top: 2 + minimum: 0 + maximum: 100 + step: 1 + &disableScroll: true + + HorizontalScrollBar + id: scroll2 + anchors.left: title.horizontalCenter + anchors.right: title.right + anchors.top: prev.top + margin-left: 2 + minimum: 0 + maximum: 100 + step: 1 + &disableScroll: true + + BotTextEdit + id: text + anchors.left: parent.left + anchors.right: parent.right + anchors.top: scroll1.bottom + margin-top: 3 + margin-left: 2 + margin-right: 1 + +SingleScrollItemPanel < Panel + height: 45 + margin-top: 2 + + BotItem + id: item + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 3 + + SmallBotSwitch + id: title + anchors.left: prev.right + anchors.right: parent.right + anchors.top: prev.top + margin-left: 2 + text-align: center + + HorizontalScrollBar + id: scroll + anchors.left: title.left + anchors.right: title.right + anchors.top: title.bottom + margin-top: 2 + minimum: 0 + maximum: 100 + step: 1 + &disableScroll: true + +DualScrollItemPanel < Panel + height: 33 + margin-top: 3 + + BotItem + id: item + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 3 + + SmallBotSwitch + id: title + anchors.left: prev.right + anchors.right: parent.right + anchors.top: prev.top + margin-left: 2 + text-align: center + + HorizontalScrollBar + id: scroll1 + anchors.left: title.left + anchors.right: title.horizontalCenter + anchors.top: title.bottom + margin-top: 2 + margin-right: 2 + minimum: 0 + maximum: 100 + step: 1 + &disableScroll: true + + HorizontalScrollBar + id: scroll2 + anchors.left: title.horizontalCenter + anchors.right: title.right + anchors.top: prev.top + margin-left: 2 + minimum: 0 + maximum: 100 + step: 1 + &disableScroll: true + +ItemsRow < Panel + height: 33 + margin-top: 2 + + BotItem + id: item1 + anchors.top: parent.top + anchors.left: parent.left + + BotItem + id: item2 + anchors.top: prev.top + anchors.left: prev.right + margin-left: 2 + + BotItem + id: item3 + anchors.top: prev.top + anchors.left: prev.right + margin-left: 2 + + BotItem + id: item4 + anchors.top: prev.top + anchors.left: prev.right + margin-left: 2 + + BotItem + id: item5 + anchors.top: prev.top + anchors.left: prev.right + margin-left: 2 + +ItemsPanel < Panel + height: 55 + + SmallBotSwitch + id: title + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-align: center + + ItemsRow + id: items + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + + +ItemAndButtonPanel < Panel + height: 40 + + BotItem + id: item + anchors.left: parent.left + anchors.top: parent.top + + BotSwitch + id: title + anchors.left: prev.right + anchors.right: parent.right + anchors.verticalCenter: prev.verticalCenter + text-align: center + margin-left: 2 + margin-top: 0 + +ItemAndSlotPanel < Panel + height: 40 + + BotItem + id: item + anchors.left: parent.left + anchors.top: parent.top + + SmallBotSwitch + id: title + anchors.left: prev.right + anchors.right: parent.right + anchors.top: prev.top + text-align: center + margin-left: 2 + margin-top: 0 + + SlotComboBox + id: slot + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 2 + height: 20 + &disableScroll: true + +TwoItemsAndSlotPanel < Panel + height: 35 + margin-top: 4 + + BotItem + id: item1 + anchors.left: parent.left + anchors.top: parent.top + margin-top: 1 + + BotItem + id: item2 + anchors.left: prev.right + anchors.top: prev.top + margin-left: 1 + + SmallBotSwitch + id: title + anchors.left: prev.right + anchors.right: parent.right + anchors.top: parent.top + text-align: center + margin-left: 2 + margin-top: 0 + + SlotComboBox + id: slot + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 2 + height: 20 + &disableScroll: true + +DualLabelPanel < Panel + height: 20 + padding: 1 + + Label + id: left + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + text-align: left + text-wrap: true + text-auto-resize: true + margin-left: 3 + font: verdana-11px-rounded + + Label + id: right + anchors.right: parent.right + anchors.left: prev.right + margin-left: 2 + anchors.top: parent.top + anchors.bottom: parent.bottom + text-align: right + text-auto-resize: true + margin-right: 3 + font: verdana-11px-rounded + +LabelAndTextEditPanel < Panel + height: 20 + padding: 1 + + Label + id: left + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.horizontalCenter + text-align: center + text-wrap: true + margin-right: 2 + + BotTextEdit + id: right + anchors.left: prev.right + margin-left: 3 + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + +SwitchAndButtonPanel < Panel + height: 20 + padding: 1 + + Button + id: right + anchors.top: parent.top + margin-top: 2 + anchors.bottom: parent.bottom + anchors.right: parent.right + text-auto-resize: true + text-align: center + + BotSwitch + id: left + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: prev.left + margin-right: 3 + text-align: center \ No newline at end of file diff --git a/800OTClient/modules/game_bugreport/bugreport.lua b/800OTClient/modules/game_bugreport/bugreport.lua new file mode 100644 index 0000000..51d631a --- /dev/null +++ b/800OTClient/modules/game_bugreport/bugreport.lua @@ -0,0 +1,36 @@ +-- TODO: find another hotkey for this. Ctrl+Z will be reserved to undo on textedits. +HOTKEY = 'Ctrl+Z' + +bugReportWindow = nil +bugTextEdit = nil + +function init() + g_ui.importStyle('bugreport') + + bugReportWindow = g_ui.createWidget('BugReportWindow', rootWidget) + bugReportWindow:hide() + + bugTextEdit = bugReportWindow:getChildById('bugTextEdit') + + g_keyboard.bindKeyDown(HOTKEY, show, modules.game_interface.getRootPanel()) +end + +function terminate() + g_keyboard.unbindKeyDown(HOTKEY, modules.game_interface.getRootPanel()) + bugReportWindow:destroy() +end + +function doReport() + g_game.reportBug(bugTextEdit:getText()) + bugReportWindow:hide() + modules.game_textmessage.displayGameMessage(tr('Bug report sent.')) +end + +function show() + if g_game.isOnline() then + bugTextEdit:setText('') + bugReportWindow:show() + bugReportWindow:raise() + bugReportWindow:focus() + end +end diff --git a/800OTClient/modules/game_bugreport/bugreport.otmod b/800OTClient/modules/game_bugreport/bugreport.otmod new file mode 100644 index 0000000..5306bb5 --- /dev/null +++ b/800OTClient/modules/game_bugreport/bugreport.otmod @@ -0,0 +1,9 @@ +Module + name: game_bugreport + description: Bug report interface (Ctrl+Z) + author: edubart + website: https://github.com/edubart/otclient + scripts: [ bugreport ] + sandboxed: true + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_bugreport/bugreport.otui b/800OTClient/modules/game_bugreport/bugreport.otui new file mode 100644 index 0000000..8ce215e --- /dev/null +++ b/800OTClient/modules/game_bugreport/bugreport.otui @@ -0,0 +1,39 @@ +BugReportWindow < MainWindow + !text: tr('Report Bug') + size: 280 250 + @onEscape: self:hide() + + Label + id: bugLabel + !text: tr('Please use this dialog to only report bugs. Do not report rule violations here!') + text-wrap: true + text-auto-resize: true + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + MultilineTextEdit + id: bugTextEdit + anchors.top: bugLabel.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: sendButton.top + margin-top: 4 + margin-bottom: 8 + + Button + id: sendButton + !text: tr('Send') + anchors.bottom: cancelButton.bottom + anchors.right: cancelButton.left + margin-right: 10 + width: 80 + &onClick: doReport + + Button + id: cancelButton + !text: tr('Cancel') + anchors.bottom: parent.bottom + anchors.right: parent.right + width: 80 + @onClick: self:getParent():hide() diff --git a/800OTClient/modules/game_buttons/buttons.lua b/800OTClient/modules/game_buttons/buttons.lua new file mode 100644 index 0000000..49a1385 --- /dev/null +++ b/800OTClient/modules/game_buttons/buttons.lua @@ -0,0 +1,49 @@ +buttonsWindow = nil +contentsPanel = nil + +function init() + buttonsWindow = g_ui.loadUI('buttons', modules.game_interface.getRightPanel()) + buttonsWindow:disableResize() + buttonsWindow:setup() + contentsPanel = buttonsWindow.contentsPanel + if not buttonsWindow.forceOpen or not contentsPanel.buttons then + buttonsWindow:close() + end +end + +function terminate() + buttonsWindow:destroy() +end + +function takeButtons(buttons) + if not buttonsWindow.forceOpen or not contentsPanel.buttons then return end + for i, button in ipairs(buttons) do + takeButton(button, true) + end + updateOrder() +end + +function takeButton(button, dontUpdateOrder) + if not buttonsWindow.forceOpen or not contentsPanel.buttons then return end + button:setParent(contentsPanel.buttons) + if not dontUpdateOrder then + updateOrder() + end +end + +function updateOrder() + local children = contentsPanel.buttons:getChildren() + table.sort(children, function(a, b) + return (a.index or 1000) < (b.index or 1000) + end) + contentsPanel.buttons:reorderChildren(children) + local visibleCount = 0 + for _, child in ipairs(children) do + if child:isVisible() then + visibleCount = visibleCount + 1 + end + end + if visibleCount > 6 and buttonsWindow:getHeight() < 30 then + buttonsWindow:setHeight(buttonsWindow:getHeight() + 22) + end +end \ No newline at end of file diff --git a/800OTClient/modules/game_buttons/buttons.otmod b/800OTClient/modules/game_buttons/buttons.otmod new file mode 100644 index 0000000..3f807e6 --- /dev/null +++ b/800OTClient/modules/game_buttons/buttons.otmod @@ -0,0 +1,8 @@ +Module + name: game_buttons + description: Shows miniwindow with buttons + author: otclient@otclient.ovh + sandboxed: true + scripts: [ buttons ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_buttons/buttons.otui b/800OTClient/modules/game_buttons/buttons.otui new file mode 100644 index 0000000..963a541 --- /dev/null +++ b/800OTClient/modules/game_buttons/buttons.otui @@ -0,0 +1,6 @@ +GameButtonsWindow + id: buttons + &save: true + !text: tr("Buttons") + icon: /images/topbuttons/buttons + diff --git a/800OTClient/modules/game_console/channelswindow.otui b/800OTClient/modules/game_console/channelswindow.otui new file mode 100644 index 0000000..94e4d40 --- /dev/null +++ b/800OTClient/modules/game_console/channelswindow.otui @@ -0,0 +1,65 @@ +ChannelListLabel < Label + font: verdana-11px-monochrome + background-color: alpha + text-offset: 2 0 + focusable: true + + $focus: + background-color: #ffffff22 + color: #ffffff + +MainWindow + id: channelsWindow + !text: tr('Channels') + size: 250 238 + @onEscape: self:destroy() + + TextList + id: channelList + vertical-scrollbar: channelsScrollBar + anchors.fill: parent + anchors.bottom: next.top + margin-bottom: 10 + padding: 1 + focusable: false + + Label + id: openPrivateChannelWithLabel + !text: tr('Open a private message channel:') + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + text-align: center + margin-bottom: 2 + + TextEdit + id: openPrivateChannelWith + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + margin-bottom: 10 + + Button + id: buttonOpen + !text: tr('Open') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + @onClick: self:getParent():onEnter() + + Button + id: buttonCancel + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: self:getParent():destroy() + + VerticalScrollBar + id: channelsScrollBar + anchors.top: channelList.top + anchors.bottom: channelList.bottom + anchors.right: channelList.right + step: 14 + pixels-scroll: true diff --git a/800OTClient/modules/game_console/communicationwindow.otui b/800OTClient/modules/game_console/communicationwindow.otui new file mode 100644 index 0000000..c5e44c0 --- /dev/null +++ b/800OTClient/modules/game_console/communicationwindow.otui @@ -0,0 +1,206 @@ +IgnoreListLabel < Label + font: verdana-11px-monochrome + background-color: alpha + text-offset: 2 0 + focusable: true + phantom: false + + $focus: + background-color: #ffffff22 + color: #ffffff + +WhiteListLabel < Label + font: verdana-11px-monochrome + background-color: alpha + text-offset: 2 0 + focusable: true + phantom: false + + $focus: + background-color: #ffffff22 + color: #ffffff + + +MainWindow + id: communicationWindow + !text: tr('Ignore List') + size: 515 410 + @onEscape: self:destroy() + + CheckBox + id: checkboxUseIgnoreList + !text: tr('Activate ignorelist') + anchors.left: parent.left + anchors.top: parent.top + width: 180 + + Label + !text: tr('Ignored Players:') + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 10 + + TextList + id: ignoreList + vertical-scrollbar: ignoreListScrollBar + anchors.left: parent.left + anchors.top: prev.bottom + height: 150 + width: 230 + margin-bottom: 10 + margin-top: 3 + padding: 1 + focusable: false + + TextEdit + id: ignoreNameEdit + anchors.top: prev.bottom + anchors.left: parent.left + width: 110 + margin-top: 5 + + Button + id: buttonIgnoreAdd + !text: tr('Add') + width: 48 + height: 20 + margin-left: 5 + anchors.top: prev.top + anchors.left: prev.right + + Button + id: buttonIgnoreRemove + !text: tr('Remove') + width: 64 + height: 20 + margin-left: 5 + anchors.top: prev.top + anchors.left: prev.right + + Label + !text: tr('Global ignore settings') + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 20 + + CheckBox + id: checkboxIgnorePrivateMessages + !text: tr('Ignore Private Messages') + anchors.left: parent.left + anchors.top: prev.bottom + width: 180 + margin-top: 5 + + CheckBox + id: checkboxIgnoreYelling + !text: tr('Ignore Yelling') + anchors.left: parent.left + anchors.top: prev.bottom + width: 180 + margin-top: 5 + + CheckBox + id: checkboxUseWhiteList + !text: tr('Activate whitelist') + anchors.top: parent.top + anchors.left: ignoreList.right + margin-left: 20 + width: 180 + + Label + !text: tr('Allowed Players:') + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 10 + + TextList + id: whiteList + vertical-scrollbar: whiteListScrollBar + anchors.left: prev.left + anchors.top: prev.bottom + height: 150 + width: 230 + margin-bottom: 10 + margin-top: 3 + padding: 1 + focusable: false + + TextEdit + id: whitelistNameEdit + anchors.top: prev.bottom + anchors.left: prev.left + width: 110 + margin-top: 5 + + Button + id: buttonWhitelistAdd + !text: tr('Add') + width: 48 + height: 20 + margin-left: 5 + anchors.top: prev.top + anchors.left: prev.right + + Button + id: buttonWhitelistRemove + !text: tr('Remove') + width: 64 + height: 20 + margin-left: 5 + anchors.top: prev.top + anchors.left: prev.right + + Label + !text: tr('Global whitelist settings') + anchors.left: whiteList.left + anchors.top: prev.bottom + margin-top: 20 + + CheckBox + id: checkboxAllowVIPs + !text: tr('Allow VIPs to message you') + anchors.left: prev.left + anchors.top: prev.bottom + width: 180 + margin-top: 5 + + Panel + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 30 + + Panel + size: 160 30 + anchors.horizontalCenter: parent.horizontalCenter + + Button + id: buttonSave + !text: tr('Save') + width: 75 + anchors.top: parent.top + anchors.left: parent.left + + Button + id: buttonCancel + !text: tr('Cancel') + width: 75 + anchors.top: parent.top + anchors.left: prev.right + margin-left: 10 + + VerticalScrollBar + id: ignoreListScrollBar + anchors.top: ignoreList.top + anchors.bottom: ignoreList.bottom + anchors.right: ignoreList.right + step: 14 + pixels-scroll: true + + VerticalScrollBar + id: whiteListScrollBar + anchors.top: whiteList.top + anchors.bottom: whiteList.bottom + anchors.right: whiteList.right + step: 14 + pixels-scroll: true diff --git a/800OTClient/modules/game_console/console.lua b/800OTClient/modules/game_console/console.lua new file mode 100644 index 0000000..f76a218 --- /dev/null +++ b/800OTClient/modules/game_console/console.lua @@ -0,0 +1,1524 @@ +SpeakTypesSettings = { + none = {}, + say = { speakType = MessageModes.Say, color = '#FFFF00' }, + whisper = { speakType = MessageModes.Whisper, color = '#FFFF00' }, + yell = { speakType = MessageModes.Yell, color = '#FFFF00' }, + broadcast = { speakType = MessageModes.GamemasterBroadcast, color = '#F55E5E' }, + private = { speakType = MessageModes.PrivateTo, color = '#5FF7F7', private = true }, + privateRed = { speakType = MessageModes.GamemasterTo, color = '#F55E5E', private = true }, + privatePlayerToPlayer = { speakType = MessageModes.PrivateTo, color = '#9F9DFD', private = true }, + privatePlayerToNpc = { speakType = MessageModes.NpcTo, color = '#9F9DFD', private = true, npcChat = true }, + privateNpcToPlayer = { speakType = MessageModes.NpcFrom, color = '#5FF7F7', private = true, npcChat = true }, + channelYellow = { speakType = MessageModes.Channel, color = '#FFFF00' }, + channelWhite = { speakType = MessageModes.ChannelManagement, color = '#FFFFFF' }, + channelRed = { speakType = MessageModes.GamemasterChannel, color = '#F55E5E' }, + channelOrange = { speakType = MessageModes.ChannelHighlight, color = '#F6A731' }, + monsterSay = { speakType = MessageModes.MonsterSay, color = '#FE6500', hideInConsole = true}, + monsterYell = { speakType = MessageModes.MonsterYell, color = '#FE6500', hideInConsole = true}, + rvrAnswerFrom = { speakType = MessageModes.RVRAnswer, color = '#FE6500' }, + rvrAnswerTo = { speakType = MessageModes.RVRAnswer, color = '#FE6500' }, + rvrContinue = { speakType = MessageModes.RVRContinue, color = '#FFFF00' }, +} + +SpeakTypes = { + [MessageModes.Say] = SpeakTypesSettings.say, + [MessageModes.Whisper] = SpeakTypesSettings.whisper, + [MessageModes.Yell] = SpeakTypesSettings.yell, + [MessageModes.GamemasterBroadcast] = SpeakTypesSettings.broadcast, + [MessageModes.PrivateFrom] = SpeakTypesSettings.private, + [MessageModes.GamemasterPrivateFrom] = SpeakTypesSettings.privateRed, + [MessageModes.NpcTo] = SpeakTypesSettings.privatePlayerToNpc, + [MessageModes.NpcFrom] = SpeakTypesSettings.privateNpcToPlayer, + [MessageModes.Channel] = SpeakTypesSettings.channelYellow, + [MessageModes.ChannelManagement] = SpeakTypesSettings.channelWhite, + [MessageModes.GamemasterChannel] = SpeakTypesSettings.channelRed, + [MessageModes.ChannelHighlight] = SpeakTypesSettings.channelOrange, + [MessageModes.MonsterSay] = SpeakTypesSettings.monsterSay, + [MessageModes.MonsterYell] = SpeakTypesSettings.monsterYell, + [MessageModes.RVRChannel] = SpeakTypesSettings.channelWhite, + [MessageModes.RVRContinue] = SpeakTypesSettings.rvrContinue, + [MessageModes.RVRAnswer] = SpeakTypesSettings.rvrAnswerFrom, + [MessageModes.NpcFromStartBlock] = SpeakTypesSettings.privateNpcToPlayer, + + -- ignored types + [MessageModes.Spell] = SpeakTypesSettings.none, + [MessageModes.BarkLow] = SpeakTypesSettings.none, + [MessageModes.BarkLoud] = SpeakTypesSettings.none, +} + +SayModes = { + [1] = { speakTypeDesc = 'whisper', icon = '/images/game/console/whisper' }, + [2] = { speakTypeDesc = 'say', icon = '/images/game/console/say' }, + [3] = { speakTypeDesc = 'yell', icon = '/images/game/console/yell' } +} + +ChannelEventFormats = { + [ChannelEvent.Join] = '%s joined the channel.', + [ChannelEvent.Leave] = '%s left the channel.', + [ChannelEvent.Invite] = '%s has been invited to the channel.', + [ChannelEvent.Exclude] = '%s has been removed from the channel.', +} + +MAX_HISTORY = 500 +MAX_LINES = 100 +HELP_CHANNEL = 9 + +consolePanel = nil +consoleContentPanel = nil +consoleTabBar = nil +consoleTextEdit = nil +consoleToggleChat = nil +channels = nil +channelsWindow = nil +communicationWindow = nil +ownPrivateName = nil +messageHistory = {} +currentMessageIndex = 0 +ignoreNpcMessages = false +defaultTab = nil +serverTab = nil +violationsChannelId = nil +violationWindow = nil +violationReportTab = nil +ignoredChannels = {} +filters = {} + +floatingMode = false + +local communicationSettings = { + useIgnoreList = true, + useWhiteList = true, + privateMessages = false, + yelling = false, + allowVIPs = false, + ignoredPlayers = {}, + whitelistedPlayers = {} +} + +function init() + connect(g_game, { + onTalk = onTalk, + onChannelList = onChannelList, + onOpenChannel = onOpenChannel, + onCloseChannel = onCloseChannel, + onChannelEvent = onChannelEvent, + onOpenPrivateChannel = onOpenPrivateChannel, + onOpenOwnPrivateChannel = onOpenOwnPrivateChannel, + onRuleViolationChannel = onRuleViolationChannel, + onRuleViolationRemove = onRuleViolationRemove, + onRuleViolationCancel = onRuleViolationCancel, + onRuleViolationLock = onRuleViolationLock, + onGameStart = online, + onGameEnd = offline, + }) + + consolePanel = g_ui.loadUI('console', modules.game_interface.getBottomPanel()) + consoleTextEdit = consolePanel:getChildById('consoleTextEdit') + consoleContentPanel = consolePanel:getChildById('consoleContentPanel') + consoleTabBar = consolePanel:getChildById('consoleTabBar') + consoleTabBar:setContentWidget(consoleContentPanel) + channels = {} + + consolePanel.onDragEnter = onDragEnter + consolePanel.onDragLeave = onDragLeave + consolePanel.onDragMove = onDragMove + consoleTabBar.onDragEnter = onDragEnter + consoleTabBar.onDragLeave = onDragLeave + consoleTabBar.onDragMove = onDragMove + + consolePanel.onKeyPress = function(self, keyCode, keyboardModifiers) + if not (keyboardModifiers == KeyboardCtrlModifier and keyCode == KeyC) then return false end + + local tab = consoleTabBar:getCurrentTab() + if not tab then return false end + + local selection = tab.tabPanel:getChildById('consoleBuffer').selectionText + if not selection then return false end + + g_window.setClipboardText(selection) + return true + end + + g_keyboard.bindKeyPress('Shift+Up', function() navigateMessageHistory(1) end, consolePanel) + g_keyboard.bindKeyPress('Shift+Down', function() navigateMessageHistory(-1) end, consolePanel) + g_keyboard.bindKeyPress('Tab', function() consoleTabBar:selectNextTab() end, consolePanel) + g_keyboard.bindKeyPress('Shift+Tab', function() consoleTabBar:selectPrevTab() end, consolePanel) + g_keyboard.bindKeyDown('Enter', sendCurrentMessage, consolePanel) + g_keyboard.bindKeyPress('Ctrl+A', function() consoleTextEdit:clearText() end, consolePanel) + + -- apply buttom functions after loaded + consoleTabBar:setNavigation(consolePanel:getChildById('prevChannelButton'), consolePanel:getChildById('nextChannelButton')) + consoleTabBar.onTabChange = onTabChange + + -- tibia like hotkeys + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.bindKeyDown('Ctrl+O', g_game.requestChannels, gameRootPanel) + g_keyboard.bindKeyDown('Ctrl+E', removeCurrentTab, gameRootPanel) + g_keyboard.bindKeyDown('Ctrl+H', openHelp, gameRootPanel) + + consoleToggleChat = consolePanel:getChildById('toggleChat') + load() + + if g_game.isOnline() then + online() + end +end + +function clearSelection(consoleBuffer) + for _,label in pairs(consoleBuffer:getChildren()) do + label:clearSelection() + end + consoleBuffer.selectionText = nil + consoleBuffer.selection = nil +end + +function selectAll(consoleBuffer) + clearSelection(consoleBuffer) + if consoleBuffer:getChildCount() > 0 then + local text = {} + for _,label in pairs(consoleBuffer:getChildren()) do + label:selectAll() + table.insert(text, label:getSelection()) + end + consoleBuffer.selectionText = table.concat(text, '\n') + consoleBuffer.selection = { first = consoleBuffer:getChildIndex(consoleBuffer:getFirstChild()), last = consoleBuffer:getChildIndex(consoleBuffer:getLastChild()) } + end +end + +function toggleChat() + if consoleToggleChat:isChecked() then + disableChat() + else + enableChat() + end +end + +function enableChat(temporarily) + if g_app.isMobile() then return end + if consoleToggleChat:isChecked() then + return consoleToggleChat:setChecked(false) + end + if not temporarily then + modules.client_options.setOption("wsadWalking", false) + end + + consoleTextEdit:setVisible(true) + consoleTextEdit:setText("") + consoleTextEdit:focus() + + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.unbindKeyDown("Enter", gameRootPanel) + + if temporarily then + local quickFunc = function() + if not g_game.isOnline() then return end + g_keyboard.unbindKeyDown("Enter", gameRootPanel) + g_keyboard.unbindKeyDown("Escape", gameRootPanel) + disableChat(temporarily) + end + g_keyboard.bindKeyDown("Enter", quickFunc, gameRootPanel) + g_keyboard.bindKeyDown("Escape", quickFunc, gameRootPanel) + end + + modules.game_walking.disableWSAD() + + consoleToggleChat:setTooltip(tr("Disable chat mode, allow to walk using ASDW")) +end + +function disableChat(temporarily) + if g_app.isMobile() then return end + if not consoleToggleChat:isChecked() then + return consoleToggleChat:setChecked(true) + end + if not temporarily then + modules.client_options.setOption("wsadWalking", true) + end + + consoleTextEdit:setVisible(false) + consoleTextEdit:setText("") + + local quickFunc = function() + if not g_game.isOnline() then return end + if consoleToggleChat:isChecked() then + consoleToggleChat:setChecked(false) + end + enableChat(true) + end + + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.bindKeyDown("Enter", quickFunc, gameRootPanel) + + modules.game_walking.enableWSAD() + + consoleToggleChat:setTooltip(tr("Enable chat mode")) +end + +function isChatEnabled() + return consoleTextEdit:isVisible() +end + +function terminate() + save() + disconnect(g_game, { + onTalk = onTalk, + onChannelList = onChannelList, + onOpenChannel = onOpenChannel, + onOpenPrivateChannel = onOpenPrivateChannel, + onOpenOwnPrivateChannel = onOpenPrivateChannel, + onCloseChannel = onCloseChannel, + onRuleViolationChannel = onRuleViolationChannel, + onRuleViolationRemove = onRuleViolationRemove, + onRuleViolationCancel = onRuleViolationCancel, + onRuleViolationLock = onRuleViolationLock, + onGameStart = online, + onGameEnd = offline, + onChannelEvent = onChannelEvent, + }) + + if g_game.isOnline() then clear() end + + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.unbindKeyDown('Ctrl+O', gameRootPanel) + g_keyboard.unbindKeyDown('Ctrl+E', gameRootPanel) + g_keyboard.unbindKeyDown('Ctrl+H', gameRootPanel) + + saveCommunicationSettings() + + if channelsWindow then + channelsWindow:destroy() + end + + if communicationWindow then + communicationWindow:destroy() + end + + if violationWindow then + violationWindow:destroy() + end + + consoleTabBar = nil + consoleContentPanel = nil + consoleToggleChat = nil + consoleTextEdit = nil + + consolePanel:destroy() + consolePanel = nil + ownPrivateName = nil + + Console = nil +end + +function save() + local settings = {} + settings.messageHistory = messageHistory + g_settings.setNode('game_console', settings) +end + +function load() + local settings = g_settings.getNode('game_console') + if settings then + messageHistory = settings.messageHistory or {} + end + loadCommunicationSettings() +end + +function onTabChange(tabBar, tab) + if tab == defaultTab or tab == serverTab then + consolePanel:getChildById('closeChannelButton'):disable() + else + consolePanel:getChildById('closeChannelButton'):enable() + end +end + +function clear() + -- save last open channels + local lastChannelsOpen = g_settings.getNode('lastChannelsOpen') or {} + local char = g_game.getCharacterName() + local savedChannels = {} + local set = false + for channelId, channelName in pairs(channels) do + if type(channelId) == 'number' then + savedChannels[channelName] = channelId + set = true + end + end + if set then + lastChannelsOpen[char] = savedChannels + else + lastChannelsOpen[char] = nil + end + g_settings.setNode('lastChannelsOpen', lastChannelsOpen) + + -- close channels + for _, channelName in pairs(channels) do + local tab = consoleTabBar:getTab(channelName) + consoleTabBar:removeTab(tab) + end + channels = {} + + consoleTabBar:removeTab(defaultTab) + defaultTab = nil + consoleTabBar:removeTab(serverTab) + serverTab = nil + + local npcTab = consoleTabBar:getTab('NPCs') + if npcTab then + consoleTabBar:removeTab(npcTab) + npcTab = nil + end + + if violationReportTab then + consoleTabBar:removeTab(violationReportTab) + violationReportTab = nil + end + + consoleTextEdit:clearText() + + if violationWindow then + violationWindow:destroy() + violationWindow = nil + end + + if channelsWindow then + channelsWindow:destroy() + channelsWindow = nil + end +end + +function switchMode(newView) + if newView then + consolePanel:setImageColor('#ffffff88') + else + consolePanel:setImageColor('white') + end + --consolePanel:setDraggable(floating) + --consoleTabBar:setDraggable(floating) + --floatingMode = floating +end + +function onDragEnter(widget, pos) + return floatingMode +end + +function onDragMove(widget, pos, moved) + if not floatingMode then + return + end + -- update margin + return true +end + +function onDragLeave(widget, pos) + return floatingMode +end + +function clearChannel(consoleTabBar) + consoleTabBar:getCurrentTab().tabPanel:getChildById('consoleBuffer'):destroyChildren() +end + +function setTextEditText(text) + consoleTextEdit:setText(text) + consoleTextEdit:setCursorPos(-1) +end + +function openHelp() + local helpChannel = 9 + if g_game.getClientVersion() <= 810 then + helpChannel = 8 + end + g_game.joinChannel(helpChannel) +end + +function openPlayerReportRuleViolationWindow() + if violationWindow or violationReportTab then return end + violationWindow = g_ui.loadUI('violationwindow', rootWidget) + violationWindow.onEscape = function() + violationWindow:destroy() + violationWindow = nil + end + violationWindow.onEnter = function() + local text = violationWindow:getChildById('text'):getText() + g_game.talkChannel(MessageModes.RVRChannel, 0, text) + violationReportTab = addTab(tr('Report Rule') .. '...', true) + addTabText(tr('Please wait patiently for a gamemaster to reply') .. '.', SpeakTypesSettings.privateRed, violationReportTab) + addTabText(applyMessagePrefixies(g_game.getCharacterName(), 0, text), SpeakTypesSettings.say, violationReportTab, g_game.getCharacterName()) + violationReportTab.locked = true + violationWindow:destroy() + violationWindow = nil + end +end + +function addTab(name, focus) + local tab = getTab(name) + if tab then -- is channel already open + if not focus then focus = true end + else + tab = consoleTabBar:addTab(name, nil, processChannelTabMenu) + end + if focus then + consoleTabBar:selectTab(tab) + end + return tab +end + +function removeTab(tab) + if type(tab) == 'string' then + tab = consoleTabBar:getTab(tab) + end + + if tab == defaultTab or tab == serverTab then + return + end + + if tab == violationReportTab then + g_game.cancelRuleViolation() + violationReportTab = nil + elseif tab.violationChatName then + g_game.closeRuleViolation(tab.violationChatName) + elseif tab.channelId then + -- notificate the server that we are leaving the channel + for k, v in pairs(channels) do + if (k == tab.channelId) then channels[k] = nil end + end + g_game.leaveChannel(tab.channelId) + elseif tab:getText() == "NPCs" then + g_game.closeNpcChannel() + end + + if getCurrentTab() == tab then + consoleTabBar:selectTab(defaultTab) + end + + consoleTabBar:removeTab(tab) +end + +function removeCurrentTab() + removeTab(consoleTabBar:getCurrentTab()) +end + +function getTab(name) + return consoleTabBar:getTab(name) +end + +function getChannelTab(channelId) + local channel = channels[channelId] + if channel then + return getTab(channel) + end + return nil +end + +function getRuleViolationsTab() + if violationsChannelId then + return getChannelTab(violationsChannelId) + end + return nil +end + +function getCurrentTab() + return consoleTabBar:getCurrentTab() +end + +function addChannel(name, id) + channels[id] = name + local focus = not table.find(ignoredChannels, id) + local tab = addTab(name, focus) + tab.channelId = id + return tab +end + +function addPrivateChannel(receiver) + channels[receiver] = receiver + return addTab(receiver, true) +end + +function addPrivateText(text, speaktype, name, isPrivateCommand, creatureName) + local focus = false + if speaktype.npcChat then + name = 'NPCs' + focus = true + end + + local privateTab = getTab(name) + if privateTab == nil then + if (modules.client_options.getOption('showPrivateMessagesInConsole') and not focus) or (isPrivateCommand and not privateTab) then + privateTab = defaultTab + else + privateTab = addTab(name, focus) + channels[name] = name + end + privateTab.npcChat = speaktype.npcChat + elseif focus then + consoleTabBar:selectTab(privateTab) + end + addTabText(text, speaktype, privateTab, creatureName) +end + +function addText(text, speaktype, tabName, creatureName) + local tab = getTab(tabName) + if tab ~= nil then + addTabText(text, speaktype, tab, creatureName) + end +end + +-- Contains letter width for font "verdana-11px-antialised" as console is based on it +local letterWidth = { -- New line (10) and Space (32) have width 1 because they are printed and not replaced with spacer + [10] = 1, [32] = 1, [33] = 3, [34] = 6, [35] = 8, [36] = 7, [37] = 13, [38] = 9, [39] = 3, [40] = 5, [41] = 5, [42] = 6, [43] = 8, [44] = 4, [45] = 5, [46] = 3, [47] = 8, + [48] = 7, [49] = 6, [50] = 7, [51] = 7, [52] = 7, [53] = 7, [54] = 7, [55] = 7, [56] = 7, [57] = 7, [58] = 3, [59] = 4, [60] = 8, [61] = 8, [62] = 8, [63] = 6, + [64] = 10, [65] = 9, [66] = 7, [67] = 7, [68] = 8, [69] = 7, [70] = 7, [71] = 8, [72] = 8, [73] = 5, [74] = 5, [75] = 7, [76] = 7, [77] = 9, [78] = 8, [79] = 8, + [80] = 7, [81] = 8, [82] = 8, [83] = 7, [84] = 8, [85] = 8, [86] = 8, [87] = 12, [88] = 8, [89] = 8, [90] = 7, [91] = 5, [92] = 8, [93] = 5, [94] = 9, [95] = 8, + [96] = 5, [97] = 7, [98] = 7, [99] = 6, [100] = 7, [101] = 7, [102] = 5, [103] = 7, [104] = 7, [105] = 3, [106] = 4, [107] = 7, [108] = 3, [109] = 11, [110] = 7, + [111] = 7, [112] = 7, [113] = 7, [114] = 6, [115] = 6, [116] = 5, [117] = 7, [118] = 8, [119] = 10, [120] = 8, [121] = 8, [122] = 6, [123] = 7, [124] = 4, [125] = 7, [126] = 8, + [127] = 1, [128] = 7, [129] = 6, [130] = 3, [131] = 7, [132] = 6, [133] = 11, [134] = 7, [135] = 7, [136] = 7, [137] = 13, [138] = 7, [139] = 4, [140] = 11, [141] = 6, [142] = 6, + [143] = 6, [144] = 6, [145] = 4, [146] = 3, [147] = 7, [148] = 6, [149] = 6, [150] = 7, [151] = 10, [152] = 7, [153] = 10, [154] = 6, [155] = 5, [156] = 11, [157] = 6, [158] = 6, + [159] = 8, [160] = 4, [161] = 3, [162] = 7, [163] = 7, [164] = 7, [165] = 8, [166] = 4, [167] = 7, [168] = 6, [169] = 10, [170] = 6, [171] = 8, [172] = 8, [173] = 16, [174] = 10, + [175] = 8, [176] = 5, [177] = 8, [178] = 5, [179] = 5, [180] = 6, [181] = 7, [182] = 7, [183] = 3, [184] = 5, [185] = 6, [186] = 6, [187] = 8, [188] = 12, [189] = 12, [190] = 12, + [191] = 6, [192] = 9, [193] = 9, [194] = 9, [195] = 9, [196] = 9, [197] = 9, [198] = 11, [199] = 7, [200] = 7, [201] = 7, [202] = 7, [203] = 7, [204] = 5, [205] = 5, [206] = 6, + [207] = 5, [208] = 8, [209] = 8, [210] = 8, [211] = 8, [212] = 8, [213] = 8, [214] = 8, [215] = 8, [216] = 8, [217] = 8, [218] = 8, [219] = 8, [220] = 8, [221] = 8, [222] = 7, + [223] = 7, [224] = 7, [225] = 7, [226] = 7, [227] = 7, [228] = 7, [229] = 7, [230] = 11, [231] = 6, [232] = 7, [233] = 7, [234] = 7, [235] = 7, [236] = 3, [237] = 4, [238] = 4, + [239] = 4, [240] = 7, [241] = 7, [242] = 7, [243] = 7, [244] = 7, [245] = 7, [246] = 7, [247] = 9, [248] = 7, [249] = 7, [250] = 7, [251] = 7, [252] = 7, [253] = 8, [254] = 7, [255] = 8 +} + +-- Return information about start, end in the string and the highlighted words +function getHighlightedText(text) + local tmpData = {} + + repeat + local tmp = {string.find(text, "{([^}]+)}", tmpData[#tmpData-1])} + for _, v in pairs(tmp) do + table.insert(tmpData, v) + end + until not(string.find(text, "{([^}]+)}", tmpData[#tmpData-1])) + + return tmpData +end + +function getNewHighlightedText(text, color, highlightColor) + local tmpData = {} + + for i, part in ipairs(text:split("{")) do + if i == 1 then + table.insert(tmpData, part) + table.insert(tmpData, color) + else + for j, part2 in ipairs(part:split("}")) do + if j == 1 then + table.insert(tmpData, part2) + table.insert(tmpData, highlightColor) + else + table.insert(tmpData, part2) + table.insert(tmpData, color) + end + end + end + end + + return tmpData +end + +function addTabText(text, speaktype, tab, creatureName) + if not tab or tab.locked or not text or #text == 0 then return end + + if modules.client_options.getOption('showTimestampsInConsole') then + text = os.date('%H:%M') .. ' ' .. text + end + + local panel = consoleTabBar:getTabPanel(tab) + local consoleBuffer = panel:getChildById('consoleBuffer') + + local label = nil + if consoleBuffer:getChildCount() > MAX_LINES then + label = consoleBuffer:getFirstChild() + consoleBuffer:moveChildToIndex(label, consoleBuffer:getChildCount()) + end + + if not label then + label = g_ui.createWidget('ConsoleLabel', consoleBuffer) + end + label:setId('consoleLabel' .. consoleBuffer:getChildCount()) + label:setText(text) + label:setColor(speaktype.color) + consoleTabBar:blinkTab(tab) + + if speaktype.npcChat and (g_game.getCharacterName() ~= creatureName or g_game.getCharacterName() == 'Account Manager') then + local highlightData = getNewHighlightedText(text, speaktype.color, "#1f9ffe") + if #highlightData > 2 then + label:setColoredText(highlightData) + end + end + + label.name = creatureName + consoleBuffer.onMouseRelease = function(self, mousePos, mouseButton) + processMessageMenu(mousePos, mouseButton, nil, nil, nil, tab) + end + label.onMouseRelease = function(self, mousePos, mouseButton) + processMessageMenu(mousePos, mouseButton, creatureName, text, self, tab) + end + label.onMousePress = function(self, mousePos, button) + if button == MouseLeftButton then clearSelection(consoleBuffer) end + end + label.onDragEnter = function(self, mousePos) + clearSelection(consoleBuffer) + return true + end + label.onDragLeave = function(self, droppedWidget, mousePos) + local text = {} + for selectionChild = consoleBuffer.selection.first, consoleBuffer.selection.last do + local label = self:getParent():getChildByIndex(selectionChild) + table.insert(text, label:getSelection()) + end + consoleBuffer.selectionText = table.concat(text, '\n') + return true + end + label.onDragMove = function(self, mousePos, mouseMoved) + local parent = self:getParent() + local parentRect = parent:getPaddingRect() + local selfIndex = parent:getChildIndex(self) + local child = parent:getChildByPos(mousePos) + + -- find bonding children + if not child then + if mousePos.y < self:getY() then + for index = selfIndex - 1, 1, -1 do + local label = parent:getChildByIndex(index) + if label:getY() + label:getHeight() > parentRect.y then + if (mousePos.y >= label:getY() and mousePos.y <= label:getY() + label:getHeight()) or index == 1 then + child = label + break + end + else + child = parent:getChildByIndex(index + 1) + break + end + end + elseif mousePos.y > self:getY() + self:getHeight() then + for index = selfIndex + 1, parent:getChildCount(), 1 do + local label = parent:getChildByIndex(index) + if label:getY() < parentRect.y + parentRect.height then + if (mousePos.y >= label:getY() and mousePos.y <= label:getY() + label:getHeight()) or index == parent:getChildCount() then + child = label + break + end + else + child = parent:getChildByIndex(index - 1) + break + end + end + else + child = self + end + end + + if not child then return false end + + local childIndex = parent:getChildIndex(child) + + -- remove old selection + clearSelection(consoleBuffer) + + -- update self selection + local textBegin = self:getTextPos(self:getLastClickPosition()) + local textPos = self:getTextPos(mousePos) + self:setSelection(textBegin, textPos) + + consoleBuffer.selection = { first = math.min(selfIndex, childIndex), last = math.max(selfIndex, childIndex) } + + -- update siblings selection + if child ~= self then + for selectionChild = consoleBuffer.selection.first + 1, consoleBuffer.selection.last - 1 do + parent:getChildByIndex(selectionChild):selectAll() + end + + local textPos = child:getTextPos(mousePos) + if childIndex > selfIndex then + child:setSelection(0, textPos) + else + child:setSelection(string.len(child:getText()), textPos) + end + end + + return true + end +end + +function removeTabLabelByName(tab, name) + local panel = consoleTabBar:getTabPanel(tab) + local consoleBuffer = panel:getChildById('consoleBuffer') + for _,label in pairs(consoleBuffer:getChildren()) do + if label.name == name then + label:destroy() + end + end +end + +function processChannelTabMenu(tab, mousePos, mouseButton) + local menu = g_ui.createWidget('PopupMenu') + menu:setGameMenu(true) + + local worldName = g_game.getWorldName() + local characterName = g_game.getCharacterName() + channelName = tab:getText() + if tab ~= defaultTab and tab ~= serverTab then + menu:addOption(tr('Close'), function() removeTab(channelName) end) + --menu:addOption(tr('Show Server Messages'), function() --[[TODO]] end) + menu:addSeparator() + end + + if consoleTabBar:getCurrentTab() == tab then + menu:addOption(tr('Clear Messages'), function() clearChannel(consoleTabBar) end) + menu:addOption(tr('Save Messages'), function() + local panel = consoleTabBar:getTabPanel(tab) + local consoleBuffer = panel:getChildById('consoleBuffer') + local lines = {} + for _,label in pairs(consoleBuffer:getChildren()) do + table.insert(lines, label:getText()) + end + + local filename = worldName .. ' - ' .. characterName .. ' - ' .. channelName .. '.txt' + local filepath = '/user_dir/' .. filename + + -- extra information at the beginning + table.insert(lines, 1, os.date('\nChannel saved at %a %b %d %H:%M:%S %Y')) + + if g_resources.fileExists(filepath) then + table.insert(lines, 1, protectedcall(g_resources.readFileContents, filepath) or '') + end + + g_resources.writeFileContents(filepath, table.concat(lines, '\n')) + modules.game_textmessage.displayStatusMessage(tr('Channel appended to %s', filename)) + end) + end + + menu:display(mousePos) +end + +function processMessageMenu(mousePos, mouseButton, creatureName, text, label, tab) + if mouseButton == MouseRightButton then + local menu = g_ui.createWidget('PopupMenu') + menu:setGameMenu(true) + if creatureName and #creatureName > 0 then + if creatureName ~= g_game.getCharacterName() then + menu:addOption(tr('Message to ' .. creatureName), function () g_game.openPrivateChannel(creatureName) end) + if not g_game.getLocalPlayer():hasVip(creatureName) then + menu:addOption(tr('Add to VIP list'), function () g_game.addVip(creatureName) end) + end + if modules.game_console.getOwnPrivateTab() then + menu:addSeparator() + menu:addOption(tr('Invite to private chat'), function() g_game.inviteToOwnChannel(creatureName) end) + menu:addOption(tr('Exclude from private chat'), function() g_game.excludeFromOwnChannel(creatureName) end) + end + if isIgnored(creatureName) then + menu:addOption(tr('Unignore') .. ' ' .. creatureName, function() removeIgnoredPlayer(creatureName) end) + else + menu:addOption(tr('Ignore') .. ' ' .. creatureName, function() addIgnoredPlayer(creatureName) end) + end + menu:addSeparator() + end + if modules.game_ruleviolation.hasWindowAccess() then + menu:addOption(tr('Rule Violation'), function() modules.game_ruleviolation.show(creatureName, text:match('.+%:%s(.+)')) end) + menu:addSeparator() + end + + menu:addOption(tr('Copy name'), function () g_window.setClipboardText(creatureName) end) + end + local selection = tab.tabPanel:getChildById('consoleBuffer').selectionText + if selection and #selection > 0 then + menu:addOption(tr('Copy'), function() g_window.setClipboardText(selection) end, '(Ctrl+C)') + end + if text then + menu:addOption(tr('Copy message'), function() g_window.setClipboardText(text) end) + end + menu:addOption(tr('Select all'), function() selectAll(tab.tabPanel:getChildById('consoleBuffer')) end) + if tab.violations and creatureName then + menu:addSeparator() + menu:addOption(tr('Process') .. ' ' .. creatureName, function() processViolation(creatureName, text) end) + menu:addOption(tr('Remove') .. ' ' .. creatureName, function() g_game.closeRuleViolation(creatureName) end) + end + menu:display(mousePos) + end +end + +function sendCurrentMessage() + local message = consoleTextEdit:getText() + if #message == 0 then return end + if not isChatEnabled() then return end + consoleTextEdit:clearText() + + -- send message + sendMessage(message) +end + +function addFilter(filter) + table.insert(filters, filter) +end + +function removeFilter(filter) + table.removevalue(filters, filter) +end + +function sendMessage(message, tab) + local tab = tab or getCurrentTab() + if not tab then return end + + for k,func in pairs(filters) do + if func(message) then + return true + end + end + + -- when talking on server log, the message goes to default channel + local name = tab:getText() + if tab == serverTab or tab == getRuleViolationsTab() then + tab = defaultTab + name = defaultTab:getText() + end + + -- handling chat commands + local channel = tab.channelId + local originalMessage = message + local chatCommandSayMode + local chatCommandPrivate + local chatCommandPrivateReady + local chatCommandMessage + + -- player used yell command + chatCommandMessage = message:match("^%#[y|Y] (.*)") + if chatCommandMessage ~= nil then + chatCommandSayMode = 'yell' + channel = 0 + message = chatCommandMessage + end + + -- player used whisper + chatCommandMessage = message:match("^%#[w|W] (.*)") + if chatCommandMessage ~= nil then + chatCommandSayMode = 'whisper' + message = chatCommandMessage + channel = 0 + end + + -- player say + chatCommandMessage = message:match("^%#[s|S] (.*)") + if chatCommandMessage ~= nil then + chatCommandSayMode = 'say' + message = chatCommandMessage + channel = 0 + end + + -- player red talk on channel + chatCommandMessage = message:match("^%#[c|C] (.*)") + if chatCommandMessage ~= nil then + chatCommandSayMode = 'channelRed' + message = chatCommandMessage + end + + -- player broadcast + chatCommandMessage = message:match("^%#[b|B] (.*)") + if chatCommandMessage ~= nil then + chatCommandSayMode = 'broadcast' + message = chatCommandMessage + channel = 0 + end + + local findIni, findEnd, chatCommandInitial, chatCommandPrivate, chatCommandEnd, chatCommandMessage = message:find("([%*%@])(.+)([%*%@])(.*)") + if findIni ~= nil and findIni == 1 then -- player used private chat command + if chatCommandInitial == chatCommandEnd then + chatCommandPrivateRepeat = false + if chatCommandInitial == "*" then + setTextEditText('*'.. chatCommandPrivate .. '* ') + end + message = chatCommandMessage:trim() + chatCommandPrivateReady = true + end + end + + message = message:gsub("^(%s*)(.*)","%2") -- remove space characters from message init + if #message == 0 then return end + + -- add new command to history + currentMessageIndex = 0 + if #messageHistory == 0 or messageHistory[#messageHistory] ~= originalMessage then + table.insert(messageHistory, originalMessage) + if #messageHistory > MAX_HISTORY then + table.remove(messageHistory, 1) + end + end + + local speaktypedesc + if (channel or tab == defaultTab) and not chatCommandPrivateReady then + if tab == defaultTab then + speaktypedesc = chatCommandSayMode or SayModes[consolePanel:getChildById('sayModeButton').sayMode].speakTypeDesc + if speaktypedesc ~= 'say' then sayModeChange(2) end -- head back to say mode + else + speaktypedesc = chatCommandSayMode or 'channelYellow' + end + + g_game.talkChannel(SpeakTypesSettings[speaktypedesc].speakType, channel, message) + return + else + local isPrivateCommand = false + local priv = true + local tabname = name + local dontAdd = false + if chatCommandPrivateReady then + speaktypedesc = 'privatePlayerToPlayer' + name = chatCommandPrivate + isPrivateCommand = true + elseif tab.npcChat then + speaktypedesc = 'privatePlayerToNpc' + elseif tab == violationReportTab then + if violationReportTab.locked then + modules.game_textmessage.displayFailureMessage('Wait for a gamemaster reply.') + dontAdd = true + else + speaktypedesc = 'rvrContinue' + tabname = tr('Report Rule') .. '...' + end + elseif tab.violationChatName then + speaktypedesc = 'rvrAnswerTo' + name = tab.violationChatName + tabname = tab.violationChatName .. '\'...' + else + speaktypedesc = 'privatePlayerToPlayer' + end + + + local speaktype = SpeakTypesSettings[speaktypedesc] + local player = g_game.getLocalPlayer() + g_game.talkPrivate(speaktype.speakType, name, message) + if not dontAdd then + message = applyMessagePrefixies(g_game.getCharacterName(), player:getLevel(), message) + addPrivateText(message, speaktype, tabname, isPrivateCommand, g_game.getCharacterName()) + end + end +end + +function sayModeChange(sayMode) + local buttom = consolePanel:getChildById('sayModeButton') + if sayMode == nil then + sayMode = buttom.sayMode + 1 + end + + if sayMode > #SayModes then sayMode = 1 end + + buttom:setIcon(SayModes[sayMode].icon) + buttom.sayMode = sayMode +end + +function getOwnPrivateTab() + if not ownPrivateName then return end + return getTab(ownPrivateName) +end + +function setIgnoreNpcMessages(ignore) + ignoreNpcMessages = ignore +end + +function navigateMessageHistory(step) + if not isChatEnabled() then + return + end + + local numCommands = #messageHistory + if numCommands > 0 then + currentMessageIndex = math.min(math.max(currentMessageIndex + step, 0), numCommands) + if currentMessageIndex > 0 then + local command = messageHistory[numCommands - currentMessageIndex + 1] + setTextEditText(command) + else + consoleTextEdit:clearText() + end + end + local player = g_game.getLocalPlayer() + if player then + player:lockWalk(200) -- lock walk for 200 ms to avoid walk during release of shift + end +end + +function applyMessagePrefixies(name, level, message) + if name and #name > 0 then + if modules.client_options.getOption('showLevelsInConsole') and level > 0 then + message = name .. ' [' .. level .. ']: ' .. message + else + message = name .. ': ' .. message + end + end + return message +end + +function onTalk(name, level, mode, message, channelId, creaturePos) + if mode == MessageModes.GamemasterBroadcast then + modules.game_textmessage.displayBroadcastMessage(name .. ': ' .. message) + return + end + + local isNpcMode = (mode == MessageModes.NpcFromStartBlock or mode == MessageModes.NpcFrom) + + if ignoreNpcMessages and isNpcMode then return end + + speaktype = SpeakTypes[mode] + + if not speaktype then + perror('unhandled onTalk message mode ' .. mode .. ': ' .. message) + return + end + + local localPlayer = g_game.getLocalPlayer() + if name ~= g_game.getCharacterName() + and isUsingIgnoreList() + and not(isUsingWhiteList()) or (isUsingWhiteList() and not(isWhitelisted(name)) and not(isAllowingVIPs() and localPlayer:hasVip(name))) then + + if mode == MessageModes.Yell and isIgnoringYelling() then + return + elseif speaktype.private and isIgnoringPrivate() and not isNpcMode then + return + elseif isIgnored(name) then + return + end + end + + if mode == MessageModes.RVRChannel then + channelId = violationsChannelId + end + + if (mode == MessageModes.Say or mode == MessageModes.Whisper or mode == MessageModes.Yell or + mode == MessageModes.Spell or mode == MessageModes.MonsterSay or mode == MessageModes.MonsterYell or + mode == MessageModes.NpcFrom or mode == MessageModes.BarkLow or mode == MessageModes.BarkLoud or + mode == MessageModes.NpcFromStartBlock) and creaturePos then + local staticText = StaticText.create() + -- Remove curly braces from screen message + local staticMessage = message + if isNpcMode then + local highlightData = getNewHighlightedText(staticMessage, speaktype.color, "#1f9ffe") + if #highlightData > 2 then + staticText:addColoredMessage(name, mode, highlightData) + else + staticText:addMessage(name, mode, staticMessage) + end + staticText:setColor(speaktype.color) + else + staticText:addMessage(name, mode, staticMessage) + end + g_map.addThing(staticText, creaturePos, -1) + end + + local defaultMessage = mode <= 3 and true or false + + if speaktype == SpeakTypesSettings.none then return end + + if speaktype.hideInConsole then return end + + local composedMessage = applyMessagePrefixies(name, level, message) + + if mode == MessageModes.RVRAnswer then + violationReportTab.locked = false + addTabText(composedMessage, speaktype, violationReportTab, name) + elseif mode == MessageModes.RVRContinue then + addText(composedMessage, speaktype, name .. '\'...', name) + elseif speaktype.private then + addPrivateText(composedMessage, speaktype, name, false, name) + if modules.client_options.getOption('showPrivateMessagesOnScreen') and speaktype ~= SpeakTypesSettings.privateNpcToPlayer then + modules.game_textmessage.displayPrivateMessage(name .. ':\n' .. message) + end + else + local channel = tr('Default') + if not defaultMessage then + channel = channels[channelId] + end + + if channel then + addText(composedMessage, speaktype, channel, name) + else + -- server sent a message on a channel that is not open + pwarning('message in channel id ' .. channelId .. ' which is unknown, this is a server bug, relogin if you want to see messages in this channel') + end + end +end + +function onOpenChannel(channelId, channelName) + addChannel(channelName, channelId) +end + +function onOpenPrivateChannel(receiver) + addPrivateChannel(receiver) +end + +function onOpenOwnPrivateChannel(channelId, channelName) + local privateTab = getTab(channelName) + if privateTab == nil then + addChannel(channelName, channelId) + end + ownPrivateName = channelName +end + +function onCloseChannel(channelId) + local channel = channels[channelId] + if channel then + local tab = getTab(channel) + if tab then + consoleTabBar:removeTab(tab) + end + for k, v in pairs(channels) do + if (k == tab.channelId) then channels[k] = nil end + end + end +end + +function processViolation(name, text) + local tabname = name .. '\'...' + local tab = addTab(tabname, true) + channels[tabname] = tabname + tab.violationChatName = name + g_game.openRuleViolation(name) + addTabText(text, SpeakTypesSettings.say, tab, name) +end + +function onRuleViolationChannel(channelId) + violationsChannelId = channelId + local tab = addChannel(tr('Rule Violations'), channelId) + tab.violations = true +end + +function onRuleViolationRemove(name) + local tab = getRuleViolationsTab() + if not tab then return end + removeTabLabelByName(tab, name) +end + +function onRuleViolationCancel(name) + local tab = getTab(name .. '\'...') + if not tab then return end + addTabText(tr('%s has finished the request', name) .. '.', SpeakTypesSettings.privateRed, tab) + tab.locked = true +end + +function onRuleViolationLock() + if not violationReportTab then return end + violationReportTab.locked = false + addTabText(tr('Your request has been closed') .. '.', SpeakTypesSettings.privateRed, violationReportTab) + violationReportTab.locked = true +end + +function doChannelListSubmit() + local channelListPanel = channelsWindow:getChildById('channelList') + local openPrivateChannelWith = channelsWindow:getChildById('openPrivateChannelWith'):getText() + if openPrivateChannelWith ~= '' then + if openPrivateChannelWith:lower() ~= g_game.getCharacterName():lower() then + g_game.openPrivateChannel(openPrivateChannelWith) + else + modules.game_textmessage.displayFailureMessage('You cannot create a private chat channel with yourself.') + end + else + local selectedChannelLabel = channelListPanel:getFocusedChild() + if not selectedChannelLabel then return end + if selectedChannelLabel.channelId == 0xFFFF then + g_game.openOwnChannel() + else + g_game.leaveChannel(selectedChannelLabel.channelId) + g_game.joinChannel(selectedChannelLabel.channelId) + end + end + + channelsWindow:destroy() +end + +function onChannelList(channelList) + if channelsWindow then channelsWindow:destroy() end + channelsWindow = g_ui.displayUI('channelswindow') + local channelListPanel = channelsWindow:getChildById('channelList') + channelsWindow.onEnter = doChannelListSubmit + channelsWindow.onDestroy = function() channelsWindow = nil end + g_keyboard.bindKeyPress('Down', function() channelListPanel:focusNextChild(KeyboardFocusReason) end, channelsWindow) + g_keyboard.bindKeyPress('Up', function() channelListPanel:focusPreviousChild(KeyboardFocusReason) end, channelsWindow) + + for k,v in pairs(channelList) do + local channelId = v[1] + local channelName = v[2] + + if #channelName > 0 then + local label = g_ui.createWidget('ChannelListLabel', channelListPanel) + label.channelId = channelId + label:setText(channelName) + + label:setPhantom(false) + label.onDoubleClick = doChannelListSubmit + end + end +end + +function loadCommunicationSettings() + communicationSettings.whitelistedPlayers = {} + communicationSettings.ignoredPlayers = {} + + local ignoreNode = g_settings.getNode('IgnorePlayers') + if ignoreNode then + for _, player in pairs(ignoreNode) do + table.insert(communicationSettings.ignoredPlayers, player) + end + end + + local whitelistNode = g_settings.getNode('WhitelistedPlayers') + if whitelistNode then + for _, player in pairs(whitelistNode) do + table.insert(communicationSettings.whitelistedPlayers, player) + end + end + + communicationSettings.useIgnoreList = g_settings.getBoolean('UseIgnoreList') + communicationSettings.useWhiteList = g_settings.getBoolean('UseWhiteList') + communicationSettings.privateMessages = g_settings.getBoolean('IgnorePrivateMessages') + communicationSettings.yelling = g_settings.getBoolean('IgnoreYelling') + communicationSettings.allowVIPs = g_settings.getBoolean('AllowVIPs') +end + +function saveCommunicationSettings() + local tmpIgnoreList = {} + local ignoredPlayers = getIgnoredPlayers() + for i = 1, #ignoredPlayers do + table.insert(tmpIgnoreList, ignoredPlayers[i]) + end + + local tmpWhiteList = {} + local whitelistedPlayers = getWhitelistedPlayers() + for i = 1, #whitelistedPlayers do + table.insert(tmpWhiteList, whitelistedPlayers[i]) + end + + g_settings.set('UseIgnoreList', communicationSettings.useIgnoreList) + g_settings.set('UseWhiteList', communicationSettings.useWhiteList) + g_settings.set('IgnorePrivateMessages', communicationSettings.privateMessages) + g_settings.set('IgnoreYelling', communicationSettings.yelling) + g_settings.setNode('IgnorePlayers', tmpIgnoreList) + g_settings.setNode('WhitelistedPlayers', tmpWhiteList) +end + +function getIgnoredPlayers() + return communicationSettings.ignoredPlayers +end + +function getWhitelistedPlayers() + return communicationSettings.whitelistedPlayers +end + +function isUsingIgnoreList() + return communicationSettings.useIgnoreList +end + +function isUsingWhiteList() + return communicationSettings.useWhiteList +end +function isIgnored(name) + return table.find(communicationSettings.ignoredPlayers, name, true) +end + +function addIgnoredPlayer(name) + if isIgnored(name) then return end + table.insert(communicationSettings.ignoredPlayers, name) + communicationSettings.useIgnoreList = true +end + +function removeIgnoredPlayer(name) + table.removevalue(communicationSettings.ignoredPlayers, name) +end + +function isWhitelisted(name) + return table.find(communicationSettings.whitelistedPlayers, name, true) +end + +function addWhitelistedPlayer(name) + if isWhitelisted(name) then return end + table.insert(communicationSettings.whitelistedPlayers, name) +end + +function removeWhitelistedPlayer(name) + table.removevalue(communicationSettings.whitelistedPlayers, name) +end + +function isIgnoringPrivate() + return communicationSettings.privateMessages +end + +function isIgnoringYelling() + return communicationSettings.yelling +end + +function isAllowingVIPs() + return communicationSettings.allowVIPs +end + +function onClickIgnoreButton() + if communicationWindow then return end + communicationWindow = g_ui.displayUI('communicationwindow') + local ignoreListPanel = communicationWindow:getChildById('ignoreList') + local whiteListPanel = communicationWindow:getChildById('whiteList') + communicationWindow.onDestroy = function() communicationWindow = nil end + + local useIgnoreListBox = communicationWindow:getChildById('checkboxUseIgnoreList') + useIgnoreListBox:setChecked(communicationSettings.useIgnoreList) + local useWhiteListBox = communicationWindow:getChildById('checkboxUseWhiteList') + useWhiteListBox:setChecked(communicationSettings.useWhiteList) + + local removeIgnoreButton = communicationWindow:getChildById('buttonIgnoreRemove') + removeIgnoreButton:disable() + ignoreListPanel.onChildFocusChange = function() removeIgnoreButton:enable() end + removeIgnoreButton.onClick = function() + local selection = ignoreListPanel:getFocusedChild() + if selection then + ignoreListPanel:removeChild(selection) + selection:destroy() + end + removeIgnoreButton:disable() + end + + local removeWhitelistButton = communicationWindow:getChildById('buttonWhitelistRemove') + removeWhitelistButton:disable() + whiteListPanel.onChildFocusChange = function() removeWhitelistButton:enable() end + removeWhitelistButton.onClick = function() + local selection = whiteListPanel:getFocusedChild() + if selection then + whiteListPanel:removeChild(selection) + selection:destroy() + end + removeWhitelistButton:disable() + end + + local newlyIgnoredPlayers = {} + local addIgnoreName = communicationWindow:getChildById('ignoreNameEdit') + local addIgnoreButton = communicationWindow:getChildById('buttonIgnoreAdd') + local addIgnoreFunction = function() + local newEntry = addIgnoreName:getText() + if newEntry == '' then return end + if table.find(getIgnoredPlayers(), newEntry) then return end + if table.find(newlyIgnoredPlayers, newEntry) then return end + local label = g_ui.createWidget('IgnoreListLabel', ignoreListPanel) + label:setText(newEntry) + table.insert(newlyIgnoredPlayers, newEntry) + addIgnoreName:setText('') + end + addIgnoreButton.onClick = addIgnoreFunction + + local newlyWhitelistedPlayers = {} + local addWhitelistName = communicationWindow:getChildById('whitelistNameEdit') + local addWhitelistButton = communicationWindow:getChildById('buttonWhitelistAdd') + local addWhitelistFunction = function() + local newEntry = addWhitelistName:getText() + if newEntry == '' then return end + if table.find(getWhitelistedPlayers(), newEntry) then return end + if table.find(newlyWhitelistedPlayers, newEntry) then return end + local label = g_ui.createWidget('WhiteListLabel', whiteListPanel) + label:setText(newEntry) + table.insert(newlyWhitelistedPlayers, newEntry) + addWhitelistName:setText('') + end + addWhitelistButton.onClick = addWhitelistFunction + + communicationWindow.onEnter = function() + if addWhitelistName:isFocused() then + addWhitelistFunction() + elseif addIgnoreName:isFocused() then + addIgnoreFunction() + end + end + + local ignorePrivateMessageBox = communicationWindow:getChildById('checkboxIgnorePrivateMessages') + ignorePrivateMessageBox:setChecked(communicationSettings.privateMessages) + local ignoreYellingBox = communicationWindow:getChildById('checkboxIgnoreYelling') + ignoreYellingBox:setChecked(communicationSettings.yelling) + local allowVIPsBox = communicationWindow:getChildById('checkboxAllowVIPs') + allowVIPsBox:setChecked(communicationSettings.allowVIPs) + + local saveButton = communicationWindow:recursiveGetChildById('buttonSave') + saveButton.onClick = function() + communicationSettings.ignoredPlayers = {} + for i = 1, ignoreListPanel:getChildCount() do + addIgnoredPlayer(ignoreListPanel:getChildByIndex(i):getText()) + end + + communicationSettings.whitelistedPlayers = {} + for i = 1, whiteListPanel:getChildCount() do + addWhitelistedPlayer(whiteListPanel:getChildByIndex(i):getText()) + end + + communicationSettings.useIgnoreList = useIgnoreListBox:isChecked() + communicationSettings.useWhiteList = useWhiteListBox:isChecked() + communicationSettings.yelling = ignoreYellingBox:isChecked() + communicationSettings.privateMessages = ignorePrivateMessageBox:isChecked() + communicationSettings.allowVIPs = allowVIPsBox:isChecked() + communicationWindow:destroy() + end + + local cancelButton = communicationWindow:recursiveGetChildById('buttonCancel') + cancelButton.onClick = function() + communicationWindow:destroy() + end + + local ignoredPlayers = getIgnoredPlayers() + for i = 1, #ignoredPlayers do + local label = g_ui.createWidget('IgnoreListLabel', ignoreListPanel) + label:setText(ignoredPlayers[i]) + end + + local whitelistedPlayers = getWhitelistedPlayers() + for i = 1, #whitelistedPlayers do + local label = g_ui.createWidget('WhiteListLabel', whiteListPanel) + label:setText(whitelistedPlayers[i]) + end +end + +function online() + defaultTab = addTab(tr('Default'), true) + serverTab = addTab(tr('Server Log'), false) + + + if g_game.getClientVersion() >= 820 then + local tab = addTab("NPCs", false) + tab.npcChat = true + end + + if g_game.getClientVersion() < 862 then + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.bindKeyDown('Ctrl+R', openPlayerReportRuleViolationWindow, gameRootPanel) + end + -- open last channels + local lastChannelsOpen = g_settings.getNode('lastChannelsOpen') + if lastChannelsOpen then + local savedChannels = lastChannelsOpen[g_game.getCharacterName()] + if savedChannels then + for channelName, channelId in pairs(savedChannels) do + channelId = tonumber(channelId) + if channelId ~= -1 and channelId < 100 then + if not table.find(channels, channelId) then + g_game.joinChannel(channelId) + table.insert(ignoredChannels, channelId) + end + end + end + end + end + scheduleEvent(function() consoleTabBar:selectTab(defaultTab) end, 500) + scheduleEvent(function() ignoredChannels = {} end, 3000) +end + +function offline() + if g_game.getClientVersion() < 862 then + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.unbindKeyDown('Ctrl+R', gameRootPanel) + end + clear() +end + +function onChannelEvent(channelId, name, type) + local fmt = ChannelEventFormats[type] + if not fmt then + print(('Unknown channel event type (%d).'):format(type)) + return + end + + local channel = channels[channelId] + if channel then + local tab = getTab(channel) + if tab then + addTabText(fmt:format(name), SpeakTypesSettings.channelOrange, tab) + end + end +end diff --git a/800OTClient/modules/game_console/console.otmod b/800OTClient/modules/game_console/console.otmod new file mode 100644 index 0000000..361e5d7 --- /dev/null +++ b/800OTClient/modules/game_console/console.otmod @@ -0,0 +1,9 @@ +Module + name: game_console + description: Manage chat window + author: edubart, andrefaramir, baxnie, sn4ake, BeniS + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ console ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_console/console.otui b/800OTClient/modules/game_console/console.otui new file mode 100644 index 0000000..6f800e8 --- /dev/null +++ b/800OTClient/modules/game_console/console.otui @@ -0,0 +1,3 @@ +ConsolePanel + id: consolePanel + phantom: false diff --git a/800OTClient/modules/game_console/violationwindow.otui b/800OTClient/modules/game_console/violationwindow.otui new file mode 100644 index 0000000..19c55ca --- /dev/null +++ b/800OTClient/modules/game_console/violationwindow.otui @@ -0,0 +1,40 @@ +MainWindow + id: ignoreWindow + !text: tr('Report Rule Violation') + size: 300 240 + + Label + !text: tr('Please state the rule violation in one clear sentence and wait for a reply from a gamemaster. Please note that your message will disappear if you close the channel.') + text-wrap: true + text-auto-resize: true + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + + TextEdit + id: text + text-wrap: true + multiline: true + anchors.top: prev.bottom + anchors.bottom: next.top + anchors.left: parent.left + anchors.right: parent.right + margin: 8 0 + max-length: 255 + + Button + id: buttonOk + !text: tr('Ok') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + @onClick: self:getParent():onEnter() + + Button + id: buttonCancel + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: self:getParent():onEscape() \ No newline at end of file diff --git a/800OTClient/modules/game_containers/containers.lua b/800OTClient/modules/game_containers/containers.lua new file mode 100644 index 0000000..0fe6c64 --- /dev/null +++ b/800OTClient/modules/game_containers/containers.lua @@ -0,0 +1,198 @@ +local gameStart = 0 + +function init() + connect(Container, { onOpen = onContainerOpen, + onClose = onContainerClose, + onSizeChange = onContainerChangeSize, + onUpdateItem = onContainerUpdateItem }) + connect(g_game, { + onGameStart = markStart, + onGameEnd = clean + }) + + reloadContainers() +end + +function terminate() + disconnect(Container, { onOpen = onContainerOpen, + onClose = onContainerClose, + onSizeChange = onContainerChangeSize, + onUpdateItem = onContainerUpdateItem }) + disconnect(g_game, { + onGameStart = markStart, + onGameEnd = clean + }) +end + +function reloadContainers() + clean() + for _,container in pairs(g_game.getContainers()) do + onContainerOpen(container) + end +end + +function clean() + for containerid,container in pairs(g_game.getContainers()) do + destroy(container) + end +end + +function markStart() + gameStart = g_clock.millis() +end + +function destroy(container) + if container.window then + container.window:destroy() + container.window = nil + container.itemsPanel = nil + end +end + +function refreshContainerItems(container) + for slot=0,container:getCapacity()-1 do + local itemWidget = container.itemsPanel:getChildById('item' .. slot) + itemWidget:setItem(container:getItem(slot)) + end + + if container:hasPages() then + refreshContainerPages(container) + end +end + +function toggleContainerPages(containerWindow, hasPages) + if hasPages == containerWindow.pagePanel:isOn() then + return + end + containerWindow.pagePanel:setOn(hasPages) + if hasPages then + containerWindow.miniwindowScrollBar:setMarginTop(containerWindow.miniwindowScrollBar:getMarginTop() + containerWindow.pagePanel:getHeight()) + containerWindow.contentsPanel:setMarginTop(containerWindow.contentsPanel:getMarginTop() + containerWindow.pagePanel:getHeight()) + else + containerWindow.miniwindowScrollBar:setMarginTop(containerWindow.miniwindowScrollBar:getMarginTop() - containerWindow.pagePanel:getHeight()) + containerWindow.contentsPanel:setMarginTop(containerWindow.contentsPanel:getMarginTop() - containerWindow.pagePanel:getHeight()) + end +end + +function refreshContainerPages(container) + local currentPage = 1 + math.floor(container:getFirstIndex() / container:getCapacity()) + local pages = 1 + math.floor(math.max(0, (container:getSize() - 1)) / container:getCapacity()) + container.window:recursiveGetChildById('pageLabel'):setText(string.format('Page %i of %i', currentPage, pages)) + + local prevPageButton = container.window:recursiveGetChildById('prevPageButton') + if currentPage == 1 then + prevPageButton:setEnabled(false) + else + prevPageButton:setEnabled(true) + prevPageButton.onClick = function() g_game.seekInContainer(container:getId(), container:getFirstIndex() - container:getCapacity()) end + end + + local nextPageButton = container.window:recursiveGetChildById('nextPageButton') + if currentPage >= pages then + nextPageButton:setEnabled(false) + else + nextPageButton:setEnabled(true) + nextPageButton.onClick = function() g_game.seekInContainer(container:getId(), container:getFirstIndex() + container:getCapacity()) end + end +end + +function onContainerOpen(container, previousContainer) + local containerWindow + if previousContainer then + containerWindow = previousContainer.window + previousContainer.window = nil + previousContainer.itemsPanel = nil + else + containerWindow = g_ui.createWidget('ContainerWindow', modules.game_interface.getContainerPanel()) + end + + containerWindow:setId('container' .. container:getId()) + if gameStart + 1000 < g_clock.millis() then + containerWindow:clearSettings() + end + + local containerPanel = containerWindow:getChildById('contentsPanel') + local containerItemWidget = containerWindow:getChildById('containerItemWidget') + containerWindow.onClose = function() + g_game.close(container) + containerWindow:hide() + end + containerWindow.onDrop = function(container, widget, mousePos) + if containerPanel:getChildByPos(mousePos) then + return false + end + local child = containerPanel:getChildByIndex(-1) + if child then + child:onDrop(widget, mousePos, true) + end + end + + -- this disables scrollbar auto hiding + local scrollbar = containerWindow:getChildById('miniwindowScrollBar') + scrollbar:mergeStyle({ ['$!on'] = { }}) + + local upButton = containerWindow:getChildById('upButton') + upButton.onClick = function() + g_game.openParent(container) + end + upButton:setVisible(container:hasParent()) + + local name = container:getName() + name = name:sub(1,1):upper() .. name:sub(2) + containerWindow:setText(name) + + containerItemWidget:setItem(container:getContainerItem()) + + containerPanel:destroyChildren() + for slot=0,container:getCapacity()-1 do + local itemWidget = g_ui.createWidget('Item', containerPanel) + itemWidget:setId('item' .. slot) + itemWidget:setItem(container:getItem(slot)) + itemWidget:setMargin(0) + itemWidget.position = container:getSlotPosition(slot) + + if not container:isUnlocked() then + itemWidget:setBorderColor('red') + end + end + + container.window = containerWindow + container.itemsPanel = containerPanel + + toggleContainerPages(containerWindow, container:hasPages()) + refreshContainerPages(container) + + local layout = containerPanel:getLayout() + local cellSize = layout:getCellSize() + containerWindow:setContentMinimumHeight(cellSize.height) + containerWindow:setContentMaximumHeight(cellSize.height*layout:getNumLines()) + + if container:hasPages() then + local height = containerWindow.miniwindowScrollBar:getMarginTop() + containerWindow.pagePanel:getHeight()+17 + if containerWindow:getHeight() < height then + containerWindow:setHeight(height) + end + end + + if not previousContainer then + local filledLines = math.max(math.ceil(container:getItemsCount() / layout:getNumColumns()), 1) + containerWindow:setContentHeight(filledLines*cellSize.height) + end + + containerWindow:setup() +end + +function onContainerClose(container) + destroy(container) +end + +function onContainerChangeSize(container, size) + if not container.window then return end + refreshContainerItems(container) +end + +function onContainerUpdateItem(container, slot, item, oldItem) + if not container.window then return end + local itemWidget = container.itemsPanel:getChildById('item' .. slot) + itemWidget:setItem(item) +end diff --git a/800OTClient/modules/game_containers/containers.otmod b/800OTClient/modules/game_containers/containers.otmod new file mode 100644 index 0000000..6e3d686 --- /dev/null +++ b/800OTClient/modules/game_containers/containers.otmod @@ -0,0 +1,9 @@ +Module + name: game_containers + description: Manage containers + author: edubart, baxnie + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [containers] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_cooldown/cooldown.lua b/800OTClient/modules/game_cooldown/cooldown.lua new file mode 100644 index 0000000..03212ef --- /dev/null +++ b/800OTClient/modules/game_cooldown/cooldown.lua @@ -0,0 +1,226 @@ +local ProgressCallback = { + update = 1, + finish = 2 +} + +cooldownWindow = nil +cooldownButton = nil +contentsPanel = nil +cooldownPanel = nil +lastPlayer = nil + +cooldown = {} +cooldowns = {} +groupCooldown = {} + +function init() + connect(g_game, { onGameStart = online, + onSpellGroupCooldown = onSpellGroupCooldown, + onSpellCooldown = onSpellCooldown }) + + cooldownButton = modules.client_topmenu.addRightGameToggleButton('cooldownButton', + tr('Cooldowns'), '/images/topbuttons/cooldowns', toggle, false, 5) + cooldownButton:setOn(true) + cooldownButton:hide() + + cooldownWindow = g_ui.loadUI('cooldown', modules.game_interface.getRightPanel()) + cooldownWindow:disableResize() + cooldownWindow:setup() + + contentsPanel = cooldownWindow:getChildById('contentsPanel') + cooldownPanel = contentsPanel:getChildById('cooldownPanel') + + -- preload cooldown images + for k,v in pairs(SpelllistSettings) do + g_textures.preload(v.iconFile) + end + + if g_game.isOnline() then + online() + end +end + +function terminate() + disconnect(g_game, { onGameStart = online, + onSpellGroupCooldown = onSpellGroupCooldown, + onSpellCooldown = onSpellCooldown }) + + for key, val in pairs(cooldowns) do + removeCooldown(key) + end + cooldowns = {} + + cooldownWindow:destroy() + cooldownButton:destroy() +end + +function loadIcon(iconId) + local spell, profile, spellName = Spells.getSpellByIcon(iconId) + if not spellName then return end + if not profile then return end + + clientIconId = Spells.getClientId(spellName) + if not clientIconId then return end + + local icon = cooldownPanel:getChildById(iconId) + if not icon then + icon = g_ui.createWidget('SpellIcon') + icon:setId(iconId) + end + + local spellSettings = SpelllistSettings[profile] + if spellSettings then + icon:setImageSource(spellSettings.iconFile) + icon:setImageClip(Spells.getImageClip(clientIconId, profile)) + else + icon = nil + end + return icon +end + +function onMiniWindowClose() + cooldownButton:setOn(false) +end + +function toggle() + if cooldownButton:isOn() then + cooldownWindow:close() + cooldownButton:setOn(false) + else + cooldownWindow:open() + cooldownButton:setOn(true) + end +end + +function online() + if g_game.getFeature(GameSpellList) then + cooldownButton:show() + else + cooldownButton:hide() + cooldownWindow:close() + end + + if not lastPlayer or lastPlayer ~= g_game.getCharacterName() then + refresh() + lastPlayer = g_game.getCharacterName() + end +end + +function refresh() + cooldownPanel:destroyChildren() +end + +function removeCooldown(progressRect) + removeEvent(progressRect.event) + if progressRect.icon then + progressRect.icon:destroy() + progressRect.icon = nil + end + cooldowns[progressRect] = nil + progressRect = nil +end + +function turnOffCooldown(progressRect) + removeEvent(progressRect.event) + if progressRect.icon then + progressRect.icon:setOn(false) + progressRect.icon = nil + end + + -- create particles + --[[local particle = g_ui.createWidget('GroupCooldownParticles', progressRect) + particle:fill('parent') + scheduleEvent(function() particle:destroy() end, 1000) -- hack until onEffectEnd]] + + cooldowns[progressRect] = nil + progressRect = nil +end + +function initCooldown(progressRect, updateCallback, finishCallback) + progressRect:setPercent(0) + + progressRect.callback = {} + progressRect.callback[ProgressCallback.update] = updateCallback + progressRect.callback[ProgressCallback.finish] = finishCallback + + updateCallback() +end + +function updateCooldown(progressRect, duration) + progressRect:setPercent(progressRect:getPercent() + 10000/duration) + + if progressRect:getPercent() < 100 then + removeEvent(progressRect.event) + + progressRect.event = scheduleEvent(function() + if not progressRect.callback then return end + progressRect.callback[ProgressCallback.update]() + end, 100) + else + progressRect.callback[ProgressCallback.finish]() + end +end + +function isGroupCooldownIconActive(groupId) + return groupCooldown[groupId] +end + +function isCooldownIconActive(iconId) + return cooldown[iconId] +end + +function onSpellCooldown(iconId, duration) + local icon = loadIcon(iconId) + if not icon then + return + end + icon:setParent(cooldownPanel) + + local progressRect = icon:getChildById(iconId) + if not progressRect then + progressRect = g_ui.createWidget('SpellProgressRect', icon) + progressRect:setId(iconId) + progressRect.icon = icon + progressRect:fill('parent') + else + progressRect:setPercent(0) + end + local spell, profile, spellName = Spells.getSpellByIcon(iconId) + progressRect:setTooltip(spellName) + + local updateFunc = function() + updateCooldown(progressRect, duration) + end + local finishFunc = function() + removeCooldown(progressRect) + cooldown[iconId] = false + end + initCooldown(progressRect, updateFunc, finishFunc) + cooldown[iconId] = true + cooldowns[progressRect] = true +end + +function onSpellGroupCooldown(groupId, duration) + if not SpellGroups[groupId] then return end + + local icon = contentsPanel:getChildById('groupIcon' .. SpellGroups[groupId]) + local progressRect = contentsPanel:getChildById('progressRect' .. SpellGroups[groupId]) + if icon then + icon:setOn(true) + removeEvent(icon.event) + end + + progressRect.icon = icon + if progressRect then + removeEvent(progressRect.event) + local updateFunc = function() + updateCooldown(progressRect, duration) + end + local finishFunc = function() + turnOffCooldown(progressRect) + groupCooldown[groupId] = false + end + initCooldown(progressRect, updateFunc, finishFunc) + groupCooldown[groupId] = true + end +end diff --git a/800OTClient/modules/game_cooldown/cooldown.otmod b/800OTClient/modules/game_cooldown/cooldown.otmod new file mode 100644 index 0000000..69cb33f --- /dev/null +++ b/800OTClient/modules/game_cooldown/cooldown.otmod @@ -0,0 +1,9 @@ +Module + name: game_cooldown + description: Spellcooldowns + author: OTClient team + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ cooldown ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_cooldown/cooldown.otui b/800OTClient/modules/game_cooldown/cooldown.otui new file mode 100644 index 0000000..31f3647 --- /dev/null +++ b/800OTClient/modules/game_cooldown/cooldown.otui @@ -0,0 +1,101 @@ +SpellGroupIcon < UIWidget + size: 22 22 + image-size: 22 22 + image-source: /images/game/spells/cooldowns + focusable: false + margin-top: 3 + +SpellIcon < UIWidget + size: 24 24 + image-size: 24 24 + focusable: false + + $!first: + margin-left: 1 + +SpellProgressRect < UIProgressRect + background: #585858AA + percent: 100 + focusable: false + +GroupCooldownParticles < UIParticles + effect: groupcooldown-effect + +MiniWindow + id: cooldownWindow + !text: tr('Spell Cooldowns') + height: 82 + icon: /images/topbuttons/cooldowns + @onClose: modules.game_cooldown.onMiniWindowClose() + &save: true + &autoOpen: false + + MiniWindowContents + SpellGroupIcon + id: groupIconAttack + image-clip: 0 0 20 20 + anchors.top: parent.top + anchors.left: parent.left + margin-left: 2 + $on: + image-clip: 0 20 20 20 + + SpellProgressRect + id: progressRectAttack + anchors.fill: groupIconAttack + !tooltip: tr('Attack') + + SpellGroupIcon + id: groupIconHealing + image-clip: 20 0 20 20 + anchors.top: parent.top + anchors.left: groupIconAttack.right + margin-left: 3 + $on: + image-clip: 20 20 20 20 + + SpellProgressRect + id: progressRectHealing + anchors.fill: groupIconHealing + !tooltip: tr('Healing') + + SpellGroupIcon + id: groupIconSupport + image-clip: 40 0 20 20 + anchors.top: parent.top + anchors.left: groupIconHealing.right + margin-left: 3 + $on: + image-clip: 40 20 20 20 + + SpellProgressRect + id: progressRectSupport + anchors.fill: groupIconSupport + !tooltip: tr('Support') + + SpellGroupIcon + id: groupIconSpecial + image-clip: 60 0 20 20 + anchors.top: parent.top + anchors.left: groupIconSupport.right + margin-left: 3 + $on: + image-clip: 60 20 20 20 + + SpellProgressRect + id: progressRectSpecial + anchors.fill: groupIconSpecial + !tooltip: tr('Special') + + Panel + id: cooldownPanel + layout: + type: horizontalBox + height: 30 + margin-top: 3 + padding: 3 + anchors.top: groupIconSpecial.bottom + anchors.left: parent.left + anchors.right: parent.right + background-color: #00000022 + diff --git a/800OTClient/modules/game_features/features.lua b/800OTClient/modules/game_features/features.lua new file mode 100644 index 0000000..30a2292 --- /dev/null +++ b/800OTClient/modules/game_features/features.lua @@ -0,0 +1,200 @@ +function init() + connect(g_game, { onClientVersionChange = updateFeatures }) +end + +function terminate() + disconnect(g_game, { onClientVersionChange = updateFeatures }) +end + +function updateFeatures(version) + g_game.resetFeatures() + if version <= 0 then + return + end + + -- you can add custom features here, list of them is in the modules\gamelib\const.lua + --g_game.enableFeature(GameClientPing) + --g_game.enableFeature(GameExtendedOpcode) + --g_game.enableFeature(GameMinimapLimitedToSingleFloor) -- it will generate minimap only for current floor + --g_game.enableFeature(GameSpritesAlphaChannel) + + --if(version >= 770) then + g_game.enableFeature(GameLooktypeU16) + g_game.enableFeature(GameMessageStatements) + g_game.enableFeature(GameLoginPacketEncryption) + --end + + --if(version >= 780) then + g_game.enableFeature(GamePlayerAddons) + g_game.enableFeature(GamePlayerStamina) + g_game.enableFeature(GameNewFluids) + g_game.enableFeature(GameMessageLevel) + g_game.enableFeature(GamePlayerStateU16) + g_game.enableFeature(GameNewOutfitProtocol) + --end + + --if(version >= 790) then + g_game.enableFeature(GameWritableDate) + --end + + if(version >= 840) then + g_game.enableFeature(GameProtocolChecksum) + g_game.enableFeature(GameAccountNames) + g_game.enableFeature(GameDoubleFreeCapacity) + end + + if(version >= 841) then + g_game.enableFeature(GameChallengeOnLogin) + g_game.enableFeature(GameMessageSizeCheck) + g_game.enableFeature(GameTileAddThingWithStackpos) + end + + if(version >= 854) then + g_game.enableFeature(GameCreatureEmblems) + end + + if(version >= 860) then + g_game.enableFeature(GameAttackSeq) + end + + if(version >= 862) then + g_game.enableFeature(GamePenalityOnDeath) + end + + if(version >= 870) then + g_game.enableFeature(GameDoubleExperience) + g_game.enableFeature(GamePlayerMounts) + g_game.enableFeature(GameSpellList) + end + + if(version >= 910) then + g_game.enableFeature(GameNameOnNpcTrade) + g_game.enableFeature(GameTotalCapacity) + g_game.enableFeature(GameSkillsBase) + g_game.enableFeature(GamePlayerRegenerationTime) + g_game.enableFeature(GameChannelPlayerList) + g_game.enableFeature(GameEnvironmentEffect) + g_game.enableFeature(GameItemAnimationPhase) + end + + if(version >= 940) then + g_game.enableFeature(GamePlayerMarket) + end + + if(version >= 953) then + g_game.enableFeature(GamePurseSlot) + g_game.enableFeature(GameClientPing) + end + + if(version >= 960) then + g_game.enableFeature(GameSpritesU32) + g_game.enableFeature(GameOfflineTrainingTime) + end + + if(version >= 963) then + g_game.enableFeature(GameAdditionalVipInfo) + end + + if(version >= 972) then + g_game.enableFeature(GameDoublePlayerGoodsMoney) + end + + if(version >= 980) then + g_game.enableFeature(GamePreviewState) + g_game.enableFeature(GameClientVersion) + end + + if(version >= 981) then + g_game.enableFeature(GameLoginPending) + g_game.enableFeature(GameNewSpeedLaw) + end + + if(version >= 984) then + g_game.enableFeature(GameContainerPagination) + g_game.enableFeature(GameBrowseField) + end + + if(version >= 1000) then + g_game.enableFeature(GameThingMarks) + g_game.enableFeature(GamePVPMode) + end + + if(version >= 1035) then + g_game.enableFeature(GameDoubleSkills) + g_game.enableFeature(GameBaseSkillU16) + end + + if(version >= 1036) then + g_game.enableFeature(GameCreatureIcons) + g_game.enableFeature(GameHideNpcNames) + end + + if(version >= 1038) then + g_game.enableFeature(GamePremiumExpiration) + end + + if(version >= 1050) then + g_game.enableFeature(GameEnhancedAnimations) + end + + if(version >= 1053) then + g_game.enableFeature(GameUnjustifiedPoints) + end + + if(version >= 1054) then + g_game.enableFeature(GameExperienceBonus) + end + + if(version >= 1055) then + g_game.enableFeature(GameDeathType) + end + + if(version >= 1057) then + g_game.enableFeature(GameIdleAnimations) + end + + if(version >= 1061) then + g_game.enableFeature(GameOGLInformation) + end + + if(version >= 1071) then + g_game.enableFeature(GameContentRevision) + end + + if(version >= 1072) then + g_game.enableFeature(GameAuthenticator) + end + + if(version >= 1074) then + g_game.enableFeature(GameSessionKey) + end + + if(version >= 1080) then + g_game.enableFeature(GameIngameStore) + end + + if(version >= 1092) then + g_game.enableFeature(GameIngameStoreServiceType) + end + + if(version >= 1093) then + g_game.enableFeature(GameIngameStoreHighlights) + end + + if(version >= 1094) then + g_game.enableFeature(GameAdditionalSkills) + end + + if(version >= 1100) then + g_game.enableFeature(GamePrey) + end + + if(version >= 1200) then + g_game.enableFeature(GameSequencedPackets) + --g_game.enableFeature(GameSendWorldName) + g_game.enableFeature(GamePlayerStateU32) + g_game.enableFeature(GameTibia12Protocol) + end + + modules.game_things.load() +end diff --git a/800OTClient/modules/game_features/features.otmod b/800OTClient/modules/game_features/features.otmod new file mode 100644 index 0000000..678cf26 --- /dev/null +++ b/800OTClient/modules/game_features/features.otmod @@ -0,0 +1,8 @@ +Module + name: game_features + description: Manager game features + reloadable: false + sandboxed: true + scripts: [features] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_healthinfo/healthinfo.lua b/800OTClient/modules/game_healthinfo/healthinfo.lua new file mode 100644 index 0000000..3ac4ea4 --- /dev/null +++ b/800OTClient/modules/game_healthinfo/healthinfo.lua @@ -0,0 +1,314 @@ +Icons = {} +Icons[PlayerStates.Poison] = { tooltip = tr('You are poisoned'), path = '/images/game/states/poisoned', id = 'condition_poisoned' } +Icons[PlayerStates.Burn] = { tooltip = tr('You are burning'), path = '/images/game/states/burning', id = 'condition_burning' } +Icons[PlayerStates.Energy] = { tooltip = tr('You are electrified'), path = '/images/game/states/electrified', id = 'condition_electrified' } +Icons[PlayerStates.Drunk] = { tooltip = tr('You are drunk'), path = '/images/game/states/drunk', id = 'condition_drunk' } +Icons[PlayerStates.ManaShield] = { tooltip = tr('You are protected by a magic shield'), path = '/images/game/states/magic_shield', id = 'condition_magic_shield' } +Icons[PlayerStates.Paralyze] = { tooltip = tr('You are paralysed'), path = '/images/game/states/slowed', id = 'condition_slowed' } +Icons[PlayerStates.Haste] = { tooltip = tr('You are hasted'), path = '/images/game/states/haste', id = 'condition_haste' } +Icons[PlayerStates.Swords] = { tooltip = tr('You may not logout during a fight'), path = '/images/game/states/logout_block', id = 'condition_logout_block' } +Icons[PlayerStates.Drowning] = { tooltip = tr('You are drowning'), path = '/images/game/states/drowning', id = 'condition_drowning' } +Icons[PlayerStates.Freezing] = { tooltip = tr('You are freezing'), path = '/images/game/states/freezing', id = 'condition_freezing' } +Icons[PlayerStates.Dazzled] = { tooltip = tr('You are dazzled'), path = '/images/game/states/dazzled', id = 'condition_dazzled' } +Icons[PlayerStates.Cursed] = { tooltip = tr('You are cursed'), path = '/images/game/states/cursed', id = 'condition_cursed' } +Icons[PlayerStates.PartyBuff] = { tooltip = tr('You are strengthened'), path = '/images/game/states/strengthened', id = 'condition_strengthened' } +Icons[PlayerStates.PzBlock] = { tooltip = tr('You may not logout or enter a protection zone'), path = '/images/game/states/protection_zone_block', id = 'condition_protection_zone_block' } +Icons[PlayerStates.Pz] = { tooltip = tr('You are within a protection zone'), path = '/images/game/states/protection_zone', id = 'condition_protection_zone' } +Icons[PlayerStates.Bleeding] = { tooltip = tr('You are bleeding'), path = '/images/game/states/bleeding', id = 'condition_bleeding' } +Icons[PlayerStates.Hungry] = { tooltip = tr('You are hungry'), path = '/images/game/states/hungry', id = 'condition_hungry' } + +healthInfoWindow = nil +healthBar = nil +manaBar = nil +experienceBar = nil +soulLabel = nil +capLabel = nil +healthTooltip = 'Your character health is %d out of %d.' +manaTooltip = 'Your character mana is %d out of %d.' +experienceTooltip = 'You have %d%% to advance to level %d.' + +overlay = nil +healthCircleFront = nil +manaCircleFront = nil +healthCircle = nil +manaCircle = nil +topHealthBar = nil +topManaBar = nil + +function init() + connect(LocalPlayer, { onHealthChange = onHealthChange, + onManaChange = onManaChange, + onLevelChange = onLevelChange, + onStatesChange = onStatesChange, + onSoulChange = onSoulChange, + onFreeCapacityChange = onFreeCapacityChange }) + + connect(g_game, { onGameEnd = offline }) + + healthInfoWindow = g_ui.loadUI('healthinfo', modules.game_interface.getRightPanel()) + healthInfoWindow:disableResize() + + if not healthInfoWindow.forceOpen then + healthInfoButton = modules.client_topmenu.addRightGameToggleButton('healthInfoButton', tr('Health Information'), '/images/topbuttons/healthinfo', toggle) + if g_app.isMobile() then + healthInfoButton:hide() + else + healthInfoButton:setOn(true) + end + end + + healthBar = healthInfoWindow:recursiveGetChildById('healthBar') + manaBar = healthInfoWindow:recursiveGetChildById('manaBar') + experienceBar = healthInfoWindow:recursiveGetChildById('experienceBar') + soulLabel = healthInfoWindow:recursiveGetChildById('soulLabel') + capLabel = healthInfoWindow:recursiveGetChildById('capLabel') + + overlay = g_ui.createWidget('HealthOverlay', modules.game_interface.getMapPanel()) + healthCircleFront = overlay:getChildById('healthCircleFront') + manaCircleFront = overlay:getChildById('manaCircleFront') + healthCircle = overlay:getChildById('healthCircle') + manaCircle = overlay:getChildById('manaCircle') + topHealthBar = overlay:getChildById('topHealthBar') + topManaBar = overlay:getChildById('topManaBar') + + connect(overlay, { onGeometryChange = onOverlayGeometryChange }) + + -- load condition icons + for k,v in pairs(Icons) do + g_textures.preload(v.path) + end + + if g_game.isOnline() then + local localPlayer = g_game.getLocalPlayer() + onHealthChange(localPlayer, localPlayer:getHealth(), localPlayer:getMaxHealth()) + onManaChange(localPlayer, localPlayer:getMana(), localPlayer:getMaxMana()) + onLevelChange(localPlayer, localPlayer:getLevel(), localPlayer:getLevelPercent()) + onStatesChange(localPlayer, localPlayer:getStates(), 0) + onSoulChange(localPlayer, localPlayer:getSoul()) + onFreeCapacityChange(localPlayer, localPlayer:getFreeCapacity()) + end + + + hideLabels() + hideExperience() + + healthInfoWindow:setup() + + if g_app.isMobile() then + healthInfoWindow:close() + healthInfoButton:setOn(false) + end +end + +function terminate() + disconnect(LocalPlayer, { onHealthChange = onHealthChange, + onManaChange = onManaChange, + onLevelChange = onLevelChange, + onStatesChange = onStatesChange, + onSoulChange = onSoulChange, + onFreeCapacityChange = onFreeCapacityChange }) + + disconnect(g_game, { onGameEnd = offline }) + disconnect(overlay, { onGeometryChange = onOverlayGeometryChange }) + + healthInfoWindow:destroy() + if healthInfoButton then + healthInfoButton:destroy() + end + overlay:destroy() +end + +function toggle() + if not healthInfoButton then return end + if healthInfoButton:isOn() then + healthInfoWindow:close() + healthInfoButton:setOn(false) + else + healthInfoWindow:open() + healthInfoButton:setOn(true) + end +end + +function toggleIcon(bitChanged) + local content = healthInfoWindow:recursiveGetChildById('conditionPanel') + + local icon = content:getChildById(Icons[bitChanged].id) + if icon then + icon:destroy() + else + icon = loadIcon(bitChanged) + icon:setParent(content) + end +end + +function loadIcon(bitChanged) + local icon = g_ui.createWidget('ConditionWidget', content) + icon:setId(Icons[bitChanged].id) + icon:setImageSource(Icons[bitChanged].path) + icon:setTooltip(Icons[bitChanged].tooltip) + return icon +end + +function offline() + healthInfoWindow:recursiveGetChildById('conditionPanel'):destroyChildren() +end + +-- hooked events +function onMiniWindowClose() + if healthInfoButton then + healthInfoButton:setOn(false) + end +end + +function onHealthChange(localPlayer, health, maxHealth) + if health > maxHealth then + maxHealth = health + end + + healthBar:setText(comma_value(health) .. ' / ' .. comma_value(maxHealth)) + healthBar:setTooltip(tr(healthTooltip, health, maxHealth)) + healthBar:setValue(health, 0, maxHealth) + + topHealthBar:setText(comma_value(health) .. ' / ' .. comma_value(maxHealth)) + topHealthBar:setTooltip(tr(healthTooltip, health, maxHealth)) + topHealthBar:setValue(health, 0, maxHealth) + + local healthPercent = math.floor(g_game.getLocalPlayer():getHealthPercent()) + local Yhppc = math.floor(208 * (1 - (healthPercent / 100))) + local rect = { x = 0, y = Yhppc, width = 63, height = 208 - Yhppc + 1 } + healthCircleFront:setImageClip(rect) + healthCircleFront:setImageRect(rect) + + if healthPercent > 92 then + healthCircleFront:setImageColor("#00BC00FF") + elseif healthPercent > 60 then + healthCircleFront:setImageColor("#50A150FF") + elseif healthPercent > 30 then + healthCircleFront:setImageColor("#A1A100FF") + elseif healthPercent > 8 then + healthCircleFront:setImageColor("#BF0A0AFF") + elseif healthPercent > 3 then + healthCircleFront:setImageColor("#910F0FFF") + else + healthCircleFront:setImageColor("#850C0CFF") + end +end + +function onManaChange(localPlayer, mana, maxMana) + if mana > maxMana then + maxMana = mana + end + + manaBar:setText(comma_value(mana) .. ' / ' .. comma_value(maxMana)) + manaBar:setTooltip(tr(manaTooltip, mana, maxMana)) + manaBar:setValue(mana, 0, maxMana) + + topManaBar:setText(comma_value(mana) .. ' / ' .. comma_value(maxMana)) + topManaBar:setTooltip(tr(manaTooltip, mana, maxMana)) + topManaBar:setValue(mana, 0, maxMana) + + local Ymppc = math.floor(208 * (1 - (math.floor((maxMana - (maxMana - mana)) * 100 / maxMana) / 100))) + local rect = { x = 0, y = Ymppc, width = 63, height = 208 - Ymppc + 1 } + manaCircleFront:setImageClip(rect) + manaCircleFront:setImageRect(rect) +end + +function onLevelChange(localPlayer, value, percent) + experienceBar:setText(percent .. '%') + experienceBar:setTooltip(tr(experienceTooltip, percent, value+1)) + experienceBar:setPercent(percent) +end + +function onSoulChange(localPlayer, soul) + soulLabel:setText(tr('Soul') .. ': ' .. soul) +end + +function onFreeCapacityChange(player, freeCapacity) + capLabel:setText(tr('Cap') .. ': ' .. freeCapacity) +end + +function onStatesChange(localPlayer, now, old) + if now == old then return end + + local bitsChanged = bit32.bxor(now, old) + for i = 1, 32 do + local pow = math.pow(2, i-1) + if pow > bitsChanged then break end + local bitChanged = bit32.band(bitsChanged, pow) + if bitChanged ~= 0 then + toggleIcon(bitChanged) + end + end +end + +-- personalization functions +function hideLabels() + local content = healthInfoWindow:recursiveGetChildById('conditionPanel') + local removeHeight = math.max(capLabel:getMarginRect().height, soulLabel:getMarginRect().height) + content:getMarginRect().height - 3 + capLabel:setOn(false) + soulLabel:setOn(false) + content:setVisible(false) + healthInfoWindow:setHeight(math.max(healthInfoWindow.minimizedHeight, healthInfoWindow:getHeight() - removeHeight)) +end + +function hideExperience() + local removeHeight = experienceBar:getMarginRect().height + experienceBar:setOn(false) + healthInfoWindow:setHeight(math.max(healthInfoWindow.minimizedHeight, healthInfoWindow:getHeight() - removeHeight)) +end + +function setHealthTooltip(tooltip) + healthTooltip = tooltip + + local localPlayer = g_game.getLocalPlayer() + if localPlayer then + healthBar:setTooltip(tr(healthTooltip, localPlayer:getHealth(), localPlayer:getMaxHealth())) + end +end + +function setManaTooltip(tooltip) + manaTooltip = tooltip + + local localPlayer = g_game.getLocalPlayer() + if localPlayer then + manaBar:setTooltip(tr(manaTooltip, localPlayer:getMana(), localPlayer:getMaxMana())) + end +end + +function setExperienceTooltip(tooltip) + experienceTooltip = tooltip + + local localPlayer = g_game.getLocalPlayer() + if localPlayer then + experienceBar:setTooltip(tr(experienceTooltip, localPlayer:getLevelPercent(), localPlayer:getLevel()+1)) + end +end + +function onOverlayGeometryChange() + if g_app.isMobile() then + topHealthBar:setMarginTop(35) + topManaBar:setMarginTop(35) + local width = overlay:getWidth() + local margin = width / 3 + 10 + topHealthBar:setMarginLeft(margin) + topManaBar:setMarginRight(margin) + return + end + + local classic = g_settings.getBoolean("classicView") + local minMargin = 40 + if classic then + topHealthBar:setMarginTop(15) + topManaBar:setMarginTop(15) + else + topHealthBar:setMarginTop(45 - overlay:getParent():getMarginTop()) + topManaBar:setMarginTop(45 - overlay:getParent():getMarginTop()) + minMargin = 200 + end + + local height = overlay:getHeight() + local width = overlay:getWidth() + + topHealthBar:setMarginLeft(math.max(minMargin, (width - height + 50) / 2 + 2)) + topManaBar:setMarginRight(math.max(minMargin, (width - height + 50) / 2 + 2)) +end \ No newline at end of file diff --git a/800OTClient/modules/game_healthinfo/healthinfo.otmod b/800OTClient/modules/game_healthinfo/healthinfo.otmod new file mode 100644 index 0000000..4c52ef0 --- /dev/null +++ b/800OTClient/modules/game_healthinfo/healthinfo.otmod @@ -0,0 +1,9 @@ +Module + name: game_healthinfo + description: Displays health, mana points, soul points, and conditions + author: edubart, BeniS + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ healthinfo ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_healthinfo/healthinfo.otui b/800OTClient/modules/game_healthinfo/healthinfo.otui new file mode 100644 index 0000000..8d55617 --- /dev/null +++ b/800OTClient/modules/game_healthinfo/healthinfo.otui @@ -0,0 +1,5 @@ +HealthInfoWindow + id: healthInfoWindow + @onClose: modules.game_healthinfo.onMiniWindowClose() + &save: true + &autoOpen: 2 diff --git a/800OTClient/modules/game_hotkeys/hotkeys_extra.lua b/800OTClient/modules/game_hotkeys/hotkeys_extra.lua new file mode 100644 index 0000000..ac066de --- /dev/null +++ b/800OTClient/modules/game_hotkeys/hotkeys_extra.lua @@ -0,0 +1,119 @@ +extraHotkeys = {} + +function addExtraHotkey(name, description, callback) + table.insert(extraHotkeys, { + name = name:lower(), + description = tr(description), + callback = callback + }) + +end + +function setupExtraHotkeys(combobox) + addExtraHotkey("none", "None", nil) + addExtraHotkey("cancelAttack", "Stop attacking", function(repeated) + if not repeated then + g_game.attack(nil) + end + end) + addExtraHotkey("attackNext", "Attack next target from battle list", function(repeated) + if repeated or not modules.game_battle then + return + end + local battlePanel = modules.game_battle.battlePanel + local attackedCreature = g_game.getAttackingCreature() + local nextChild = nil + local breakNext = false + for i, child in ipairs(battlePanel:getChildren()) do + if not child.creature or not child:isOn() then + break + end + nextChild = child + if breakNext then + break + end + if child.creature == attackedCreature then + breakNext = true + nextChild = battlePanel:getFirstChild() + end + end + if not breakNext then + nextChild = battlePanel:getFirstChild() + end + if nextChild and nextChild.creature ~= attackedCreature then + g_game.attack(nextChild.creature) + end + end) + + addExtraHotkey("attackPrevious", "Attack previous target from battle list", function(repeated) + if repeated or not modules.game_battle then + return + end + local battlePanel = modules.game_battle.battlePanel + local attackedCreature = g_game.getAttackingCreature() + local prevChild = nil + for i, child in ipairs(battlePanel:getChildren()) do + if not child.creature or not child:isOn() then + break + end + if child.creature == attackedCreature then + break + end + prevChild = child + end + if prevChild and prevChild.creature ~= attackedCreature then + g_game.attack(prevChild.creature) + end + end) + + addExtraHotkey("toogleWsad", "Enable/disable wsad walking", function(repeated) + if repeated or not modules.game_console then + return + end + if not modules.game_console.consoleToggleChat:isChecked() then + modules.game_console.disableChat(true) + else + modules.game_console.enableChat(true) + end + end) + + for index, actionDetails in ipairs(extraHotkeys) do + combobox:addOption(actionDetails.description) + end +end + +function executeExtraHotkey(action, repeated) + action = action:lower() + for index, actionDetails in ipairs(extraHotkeys) do + if actionDetails.name == action and actionDetails.callback then + actionDetails.callback(repeated) + end + end +end + +function translateActionToActionComboboxIndex(action) + action = action:lower() + for index, actionDetails in ipairs(extraHotkeys) do + if actionDetails.name == action then + return index + end + end + return 1 +end + +function translateActionComboboxIndexToAction(index) + if index > 1 and index <= #extraHotkeys then + return extraHotkeys[index].name + end + return nil +end + +function getActionDescription(action) + action = action:lower() + for index, actionDetails in ipairs(extraHotkeys) do + if actionDetails.name == action then + return actionDetails.description + end + end + return "invalid action" +end \ No newline at end of file diff --git a/800OTClient/modules/game_hotkeys/hotkeys_manager.lua b/800OTClient/modules/game_hotkeys/hotkeys_manager.lua new file mode 100644 index 0000000..e9a07c9 --- /dev/null +++ b/800OTClient/modules/game_hotkeys/hotkeys_manager.lua @@ -0,0 +1,708 @@ +HOTKEY_MANAGER_USE = nil +HOTKEY_MANAGER_USEONSELF = 1 +HOTKEY_MANAGER_USEONTARGET = 2 +HOTKEY_MANAGER_USEWITH = 3 + +HotkeyColors = { + text = '#888888', + textAutoSend = '#FFFFFF', + itemUse = '#8888FF', + itemUseSelf = '#00FF00', + itemUseTarget = '#FF0000', + itemUseWith = '#F5B325', + extraAction = '#FFAA00' +} + +hotkeysManagerLoaded = false +hotkeysWindow = nil +configSelector = nil +hotkeysButton = nil +currentHotkeyLabel = nil +currentItemPreview = nil +itemWidget = nil +addHotkeyButton = nil +removeHotkeyButton = nil +hotkeyText = nil +hotKeyTextLabel = nil +sendAutomatically = nil +selectObjectButton = nil +clearObjectButton = nil +useOnSelf = nil +useOnTarget = nil +useWith = nil +defaultComboKeys = nil +perCharacter = true +mouseGrabberWidget = nil +useRadioGroup = nil +currentHotkeys = nil +boundCombosCallback = {} +hotkeysList = {} +hotkeyConfigs = {} +currentConfig = 1 +configValueChanged = false + +-- public functions +function init() + if not g_app.isMobile() then + hotkeysButton = modules.client_topmenu.addLeftGameButton('hotkeysButton', tr('Hotkeys') .. ' (Ctrl+K)', '/images/topbuttons/hotkeys', toggle, false, 7) + end + g_keyboard.bindKeyDown('Ctrl+K', toggle) + hotkeysWindow = g_ui.displayUI('hotkeys_manager') + hotkeysWindow:setVisible(false) + + configSelector = hotkeysWindow:getChildById('configSelector') + currentHotkeys = hotkeysWindow:getChildById('currentHotkeys') + currentItemPreview = hotkeysWindow:getChildById('itemPreview') + addHotkeyButton = hotkeysWindow:getChildById('addHotkeyButton') + removeHotkeyButton = hotkeysWindow:getChildById('removeHotkeyButton') + hotkeyText = hotkeysWindow:getChildById('hotkeyText') + hotKeyTextLabel = hotkeysWindow:getChildById('hotKeyTextLabel') + sendAutomatically = hotkeysWindow:getChildById('sendAutomatically') + selectObjectButton = hotkeysWindow:getChildById('selectObjectButton') + clearObjectButton = hotkeysWindow:getChildById('clearObjectButton') + useOnSelf = hotkeysWindow:getChildById('useOnSelf') + useOnTarget = hotkeysWindow:getChildById('useOnTarget') + useWith = hotkeysWindow:getChildById('useWith') + + useRadioGroup = UIRadioGroup.create() + useRadioGroup:addWidget(useOnSelf) + useRadioGroup:addWidget(useOnTarget) + useRadioGroup:addWidget(useWith) + useRadioGroup.onSelectionChange = function(self, selected) onChangeUseType(selected) end + + mouseGrabberWidget = g_ui.createWidget('UIWidget') + mouseGrabberWidget:setVisible(false) + mouseGrabberWidget:setFocusable(false) + mouseGrabberWidget.onMouseRelease = onChooseItemMouseRelease + + currentHotkeys.onChildFocusChange = function(self, hotkeyLabel) onSelectHotkeyLabel(hotkeyLabel) end + g_keyboard.bindKeyPress('Down', function() currentHotkeys:focusNextChild(KeyboardFocusReason) end, hotkeysWindow) + g_keyboard.bindKeyPress('Up', function() currentHotkeys:focusPreviousChild(KeyboardFocusReason) end, hotkeysWindow) + + if hotkeysWindow.action and setupExtraHotkeys then + setupExtraHotkeys(hotkeysWindow.action) + end + + connect(g_game, { + onGameStart = online, + onGameEnd = offline + }) + + for i = 1, configSelector:getOptionsCount() do + hotkeyConfigs[i] = g_configs.create("/hotkeys_" .. i .. ".otml") + end + + load() +end + +function terminate() + disconnect(g_game, { + onGameStart = online, + onGameEnd = offline + }) + + g_keyboard.unbindKeyDown('Ctrl+K') + + unload() + + hotkeysWindow:destroy() + if hotkeysButton then + hotkeysButton:destroy() + end + mouseGrabberWidget:destroy() +end + +function online() + reload() + hide() +end + +function offline() + unload() + hide() +end + +function show() + if not g_game.isOnline() then + return + end + hotkeysWindow:show() + hotkeysWindow:raise() + hotkeysWindow:focus() +end + +function hide() + hotkeysWindow:hide() +end + +function toggle() + if not hotkeysWindow:isVisible() then + show() + else + hide() + end +end + +function ok() + save() + hide() +end + +function cancel() + reload() + hide() +end + +function load(forceDefaults) + hotkeysManagerLoaded = false + currentConfig = 1 + + local hotkeysNode = g_settings.getNode('hotkeys') or {} + local index = g_game.getCharacterName() .. "_" .. g_game.getClientVersion() + if hotkeysNode[index] ~= nil and hotkeysNode[index] > 0 and hotkeysNode[index] <= #hotkeyConfigs then + currentConfig = hotkeysNode[index] + end + + configSelector:setCurrentIndex(currentConfig, true) + + local hotkeySettings = hotkeyConfigs[currentConfig]:getNode('hotkeys') + local hotkeys = {} + + if not table.empty(hotkeySettings) then hotkeys = hotkeySettings end + + hotkeyList = {} + if not forceDefaults then + if not table.empty(hotkeys) then + for keyCombo, setting in pairs(hotkeys) do + keyCombo = tostring(keyCombo) + addKeyCombo(keyCombo, setting) + hotkeyList[keyCombo] = setting + end + end + end + + if currentHotkeys:getChildCount() == 0 then + loadDefautComboKeys() + end + + configValueChanged = false + hotkeysManagerLoaded = true +end + +function unload() + local gameRootPanel = modules.game_interface.getRootPanel() + for keyCombo,callback in pairs(boundCombosCallback) do + g_keyboard.unbindKeyPress(keyCombo, callback, gameRootPanel) + end + boundCombosCallback = {} + currentHotkeys:destroyChildren() + currentHotkeyLabel = nil + updateHotkeyForm(true) + hotkeyList = {} +end + +function reset() + unload() + load(true) +end + +function reload() + unload() + load() +end + +function save() + if not configValueChanged then + return + end + + local hotkeySettings = hotkeyConfigs[currentConfig]:getNode('hotkeys') or {} + + table.clear(hotkeySettings) + + for _,child in pairs(currentHotkeys:getChildren()) do + hotkeySettings[child.keyCombo] = { + autoSend = child.autoSend, + itemId = child.itemId, + subType = child.subType, + useType = child.useType, + value = child.value, + action = child.action + } + end + + hotkeyList = hotkeySettings + hotkeyConfigs[currentConfig]:setNode('hotkeys', hotkeySettings) + hotkeyConfigs[currentConfig]:save() + + local index = g_game.getCharacterName() .. "_" .. g_game.getClientVersion() + local hotkeysNode = g_settings.getNode('hotkeys') or {} + hotkeysNode[index] = currentConfig + g_settings.setNode('hotkeys', hotkeysNode) + g_settings.save() +end + +function onConfigChange() + if not configSelector then return end + local index = g_game.getCharacterName() .. "_" .. g_game.getClientVersion() + local hotkeysNode = g_settings.getNode('hotkeys') or {} + hotkeysNode[index] = configSelector.currentIndex + g_settings.setNode('hotkeys', hotkeysNode) + reload() +end + +function loadDefautComboKeys() + if not defaultComboKeys then + for i=1,12 do + addKeyCombo('F' .. i) + end + for i=1,4 do + addKeyCombo('Shift+F' .. i) + end + else + for keyCombo, keySettings in pairs(defaultComboKeys) do + addKeyCombo(keyCombo, keySettings) + end + end +end + +function setDefaultComboKeys(combo) + defaultComboKeys = combo +end + +function onChooseItemMouseRelease(self, mousePosition, mouseButton) + local item = nil + if mouseButton == MouseLeftButton then + local clickedWidget = modules.game_interface.getRootPanel():recursiveGetChildByPos(mousePosition, false) + if clickedWidget then + if clickedWidget:getClassName() == 'UIGameMap' then + local tile = clickedWidget:getTile(mousePosition) + if tile then + local thing = tile:getTopMoveThing() + if thing and thing:isItem() then + item = thing + end + end + elseif clickedWidget:getClassName() == 'UIItem' and not clickedWidget:isVirtual() then + item = clickedWidget:getItem() + end + end + end + + if item and currentHotkeyLabel then + currentHotkeyLabel.itemId = item:getId() + if item:isFluidContainer() then + currentHotkeyLabel.subType = item:getSubType() + end + if item:isMultiUse() then + currentHotkeyLabel.useType = HOTKEY_MANAGER_USEWITH + else + currentHotkeyLabel.useType = HOTKEY_MANAGER_USE + end + currentHotkeyLabel.value = nil + currentHotkeyLabel.autoSend = false + updateHotkeyLabel(currentHotkeyLabel) + updateHotkeyForm(true) + end + + show() + + g_mouse.popCursor('target') + self:ungrabMouse() + return true +end + +function startChooseItem() + if g_ui.isMouseGrabbed() then return end + mouseGrabberWidget:grabMouse() + g_mouse.pushCursor('target') + hide() +end + +function clearObject() + currentHotkeyLabel.action = nil + currentHotkeyLabel.itemId = nil + currentHotkeyLabel.subType = nil + currentHotkeyLabel.useType = nil + currentHotkeyLabel.autoSend = nil + currentHotkeyLabel.value = nil + updateHotkeyLabel(currentHotkeyLabel) + updateHotkeyForm(true) +end + +function addHotkey() + local assignWindow = g_ui.createWidget('HotkeyAssignWindow', rootWidget) + assignWindow:grabKeyboard() + + local comboLabel = assignWindow:getChildById('comboPreview') + comboLabel.keyCombo = '' + assignWindow.onKeyDown = hotkeyCapture +end + +function addKeyCombo(keyCombo, keySettings, focus) + if keyCombo == nil or #keyCombo == 0 then return end + if not keyCombo then return end + local hotkeyLabel = currentHotkeys:getChildById(keyCombo) + if not hotkeyLabel then + hotkeyLabel = g_ui.createWidget('HotkeyListLabel') + hotkeyLabel:setId(keyCombo) + + local children = currentHotkeys:getChildren() + children[#children+1] = hotkeyLabel + table.sort(children, function(a,b) + if a:getId():len() < b:getId():len() then + return true + elseif a:getId():len() == b:getId():len() then + return a:getId() < b:getId() + else + return false + end + end) + for i=1,#children do + if children[i] == hotkeyLabel then + currentHotkeys:insertChild(i, hotkeyLabel) + break + end + end + + if keySettings then + currentHotkeyLabel = hotkeyLabel + hotkeyLabel.keyCombo = keyCombo + hotkeyLabel.autoSend = toboolean(keySettings.autoSend) + hotkeyLabel.action = keySettings.action + hotkeyLabel.itemId = tonumber(keySettings.itemId) + hotkeyLabel.subType = tonumber(keySettings.subType) + hotkeyLabel.useType = tonumber(keySettings.useType) + if keySettings.value then hotkeyLabel.value = tostring(keySettings.value) end + else + hotkeyLabel.keyCombo = keyCombo + hotkeyLabel.autoSend = false + hotkeyLabel.itemId = nil + hotkeyLabel.subType = nil + hotkeyLabel.useType = nil + hotkeyLabel.action = nil + hotkeyLabel.value = '' + end + + updateHotkeyLabel(hotkeyLabel) + + local gameRootPanel = modules.game_interface.getRootPanel() + if keyCombo:lower():find("ctrl") then + if boundCombosCallback[keyCombo] then + g_keyboard.unbindKeyPress(keyCombo, boundCombosCallback[keyCombo], gameRootPanel) + end + end + + boundCombosCallback[keyCombo] = function(k, c, ticks) prepareKeyCombo(keyCombo, ticks) end + g_keyboard.bindKeyPress(keyCombo, boundCombosCallback[keyCombo], gameRootPanel) + + if not keyCombo:lower():find("ctrl") then + local keyComboCtrl = "Ctrl+" .. keyCombo + if not boundCombosCallback[keyComboCtrl] then + boundCombosCallback[keyComboCtrl] = function(k, c, ticks) prepareKeyCombo(keyComboCtrl, ticks) end + g_keyboard.bindKeyPress(keyComboCtrl, boundCombosCallback[keyComboCtrl], gameRootPanel) + end + end + end + + if focus then + currentHotkeys:focusChild(hotkeyLabel) + currentHotkeys:ensureChildVisible(hotkeyLabel) + updateHotkeyForm(true) + end + configValueChanged = true +end + +function prepareKeyCombo(keyCombo, ticks) + local hotKey = hotkeyList[keyCombo] + if (keyCombo:lower():find("ctrl") and not hotKey) or (hotKey and hotKey.itemId == nil and (not hotKey.value or #hotKey.value == 0) and not hotKey.action) then + keyCombo = keyCombo:gsub("Ctrl%+", "") + keyCombo = keyCombo:gsub("ctrl%+", "") + hotKey = hotkeyList[keyCombo] + end + if not hotKey then + return + end + + if hotKey.itemId == nil and hotKey.action == nil then -- say + scheduleEvent(function() doKeyCombo(keyCombo, ticks >= 5) end, g_settings.getNumber('hotkeyDelay')) + else + doKeyCombo(keyCombo, ticks >= 5) + end +end + +function doKeyCombo(keyCombo, repeated) + if not g_game.isOnline() then return end + if modules.game_console and modules.game_console.isChatEnabled() then + if keyCombo:len() == 1 then + return + end + end + if modules.game_walking then + modules.game_walking.checkTurn() + end + + local hotKey = hotkeyList[keyCombo] + if not hotKey then return end + + local hotkeyDelay = 100 + if hotKey.hotkeyDelayTo == nil or g_clock.millis() > hotKey.hotkeyDelayTo + hotkeyDelay then + hotkeyDelay = 200 -- for first use + end + if hotKey.hotkeyDelayTo ~= nil and g_clock.millis() < hotKey.hotkeyDelayTo then + return + end + if hotKey.action then + executeExtraHotkey(hotKey.action, repeated) + elseif hotKey.itemId == nil then + if not hotKey.value or #hotKey.value == 0 then return end + if hotKey.autoSend then + modules.game_console.sendMessage(hotKey.value) + else + modules.game_console.setTextEditText(hotKey.value) + end + hotKey.hotkeyDelayTo = g_clock.millis() + hotkeyDelay + elseif hotKey.useType == HOTKEY_MANAGER_USE then + if g_game.getClientVersion() < 780 then + local item = g_game.findPlayerItem(hotKey.itemId, hotKey.subType or -1) + if item then + g_game.use(item) + end + else + g_game.useInventoryItem(hotKey.itemId) + end + hotKey.hotkeyDelayTo = g_clock.millis() + hotkeyDelay + elseif hotKey.useType == HOTKEY_MANAGER_USEONSELF then + if g_game.getClientVersion() < 780 then + local item = g_game.findPlayerItem(hotKey.itemId, hotKey.subType or -1) + if item then + g_game.useWith(item, g_game.getLocalPlayer()) + end + else + g_game.useInventoryItemWith(hotKey.itemId, g_game.getLocalPlayer(), hotKey.subType or -1) + end + hotKey.hotkeyDelayTo = g_clock.millis() + hotkeyDelay + elseif hotKey.useType == HOTKEY_MANAGER_USEONTARGET then + local attackingCreature = g_game.getAttackingCreature() + if not attackingCreature then + local item = Item.create(hotKey.itemId) + if g_game.getClientVersion() < 780 then + local tmpItem = g_game.findPlayerItem(hotKey.itemId, hotKey.subType or -1) + if not tmpItem then return end + item = tmpItem + end + + modules.game_interface.startUseWith(item, hotKey.subType or - 1) + return + end + + if not attackingCreature:getTile() then return end + if g_game.getClientVersion() < 780 then + local item = g_game.findPlayerItem(hotKey.itemId, hotKey.subType or -1) + if item then + g_game.useWith(item, attackingCreature, hotKey.subType or -1) + end + else + g_game.useInventoryItemWith(hotKey.itemId, attackingCreature, hotKey.subType or -1) + end + hotKey.hotkeyDelayTo = g_clock.millis() + hotkeyDelay + elseif hotKey.useType == HOTKEY_MANAGER_USEWITH then + local item = Item.create(hotKey.itemId) + if g_game.getClientVersion() < 780 then + local tmpItem = g_game.findPlayerItem(hotKey.itemId, hotKey.subType or -1) + if not tmpItem then return true end + item = tmpItem + end + modules.game_interface.startUseWith(item, hotKey.subType or - 1) + end +end + +function updateHotkeyLabel(hotkeyLabel) + if not hotkeyLabel then return end + if hotkeyLabel.action ~= nil then + hotkeyLabel:setText(tr('%s: (Action: %s)', hotkeyLabel.keyCombo, getActionDescription(hotkeyLabel.action))) + hotkeyLabel:setColor(HotkeyColors.extraAction) + elseif hotkeyLabel.useType == HOTKEY_MANAGER_USEONSELF then + hotkeyLabel:setText(tr('%s: (use object on yourself)', hotkeyLabel.keyCombo)) + hotkeyLabel:setColor(HotkeyColors.itemUseSelf) + elseif hotkeyLabel.useType == HOTKEY_MANAGER_USEONTARGET then + hotkeyLabel:setText(tr('%s: (use object on target)', hotkeyLabel.keyCombo)) + hotkeyLabel:setColor(HotkeyColors.itemUseTarget) + elseif hotkeyLabel.useType == HOTKEY_MANAGER_USEWITH then + hotkeyLabel:setText(tr('%s: (use object with crosshair)', hotkeyLabel.keyCombo)) + hotkeyLabel:setColor(HotkeyColors.itemUseWith) + elseif hotkeyLabel.itemId ~= nil then + hotkeyLabel:setText(tr('%s: (use object)', hotkeyLabel.keyCombo)) + hotkeyLabel:setColor(HotkeyColors.itemUse) + else + local text = hotkeyLabel.keyCombo .. ': ' + if hotkeyLabel.value then + text = text .. hotkeyLabel.value + end + hotkeyLabel:setText(text) + if hotkeyLabel.autoSend then + hotkeyLabel:setColor(HotkeyColors.autoSend) + else + hotkeyLabel:setColor(HotkeyColors.text) + end + end +end + +function updateHotkeyForm(reset) + configValueChanged = true + if hotkeysWindow.action then + if currentHotkeyLabel then + hotkeysWindow.action:enable() + if currentHotkeyLabel.action then + hotkeysWindow.action:setCurrentIndex(translateActionToActionComboboxIndex(currentHotkeyLabel.action), true) + else + hotkeysWindow.action:setCurrentIndex(1, true) + end + else + hotkeysWindow.action:disable() + hotkeysWindow.action:setCurrentIndex(1, true) + end + end + local hasCustomAction = hotkeysWindow.action and hotkeysWindow.action.currentIndex > 1 + if currentHotkeyLabel and not hasCustomAction then + removeHotkeyButton:enable() + if currentHotkeyLabel.itemId ~= nil then + hotkeyText:clearText() + hotkeyText:disable() + hotKeyTextLabel:disable() + sendAutomatically:setChecked(false) + sendAutomatically:disable() + selectObjectButton:disable() + clearObjectButton:enable() + currentItemPreview:setItemId(currentHotkeyLabel.itemId) + if currentHotkeyLabel.subType then + currentItemPreview:setItemSubType(currentHotkeyLabel.subType) + end + if currentItemPreview:getItem():isMultiUse() then + useOnSelf:enable() + useOnTarget:enable() + useWith:enable() + if currentHotkeyLabel.useType == HOTKEY_MANAGER_USEONSELF then + useRadioGroup:selectWidget(useOnSelf) + elseif currentHotkeyLabel.useType == HOTKEY_MANAGER_USEONTARGET then + useRadioGroup:selectWidget(useOnTarget) + elseif currentHotkeyLabel.useType == HOTKEY_MANAGER_USEWITH then + useRadioGroup:selectWidget(useWith) + end + else + useOnSelf:disable() + useOnTarget:disable() + useWith:disable() + useRadioGroup:clearSelected() + end + else + useOnSelf:disable() + useOnTarget:disable() + useWith:disable() + useRadioGroup:clearSelected() + hotkeyText:enable() + hotkeyText:focus() + hotKeyTextLabel:enable() + if reset then + hotkeyText:setCursorPos(-1) + end + hotkeyText:setText(currentHotkeyLabel.value) + sendAutomatically:setChecked(currentHotkeyLabel.autoSend) + sendAutomatically:setEnabled(currentHotkeyLabel.value and #currentHotkeyLabel.value > 0) + selectObjectButton:enable() + clearObjectButton:disable() + currentItemPreview:clearItem() + end + else + removeHotkeyButton:disable() + hotkeyText:disable() + sendAutomatically:disable() + selectObjectButton:disable() + clearObjectButton:disable() + useOnSelf:disable() + useOnTarget:disable() + useWith:disable() + hotkeyText:clearText() + useRadioGroup:clearSelected() + sendAutomatically:setChecked(false) + currentItemPreview:clearItem() + end +end + +function removeHotkey() + if currentHotkeyLabel == nil then return end + local gameRootPanel = modules.game_interface.getRootPanel() + configValueChanged = true + g_keyboard.unbindKeyPress(currentHotkeyLabel.keyCombo, boundCombosCallback[currentHotkeyLabel.keyCombo], gameRootPanel) + boundCombosCallback[currentHotkeyLabel.keyCombo] = nil + currentHotkeyLabel:destroy() + currentHotkeyLabel = nil +end + +function updateHotkeyAction() + if not hotkeysManagerLoaded then return end + if currentHotkeyLabel == nil then return end + configValueChanged = true + currentHotkeyLabel.action = translateActionComboboxIndexToAction(hotkeysWindow.action.currentIndex) + updateHotkeyLabel(currentHotkeyLabel) + updateHotkeyForm() +end + +function onHotkeyTextChange(value) + if not hotkeysManagerLoaded then return end + if currentHotkeyLabel == nil then return end + currentHotkeyLabel.value = value + if value == '' then + currentHotkeyLabel.autoSend = false + end + configValueChanged = true + updateHotkeyLabel(currentHotkeyLabel) + updateHotkeyForm() +end + +function onSendAutomaticallyChange(autoSend) + if not hotkeysManagerLoaded then return end + if currentHotkeyLabel == nil then return end + if not currentHotkeyLabel.value or #currentHotkeyLabel.value == 0 then return end + configValueChanged = true + currentHotkeyLabel.autoSend = autoSend + updateHotkeyLabel(currentHotkeyLabel) + updateHotkeyForm() +end + +function onChangeUseType(useTypeWidget) + if not hotkeysManagerLoaded then return end + if currentHotkeyLabel == nil then return end + configValueChanged = true + if useTypeWidget == useOnSelf then + currentHotkeyLabel.useType = HOTKEY_MANAGER_USEONSELF + elseif useTypeWidget == useOnTarget then + currentHotkeyLabel.useType = HOTKEY_MANAGER_USEONTARGET + elseif useTypeWidget == useWith then + currentHotkeyLabel.useType = HOTKEY_MANAGER_USEWITH + else + currentHotkeyLabel.useType = HOTKEY_MANAGER_USE + end + updateHotkeyLabel(currentHotkeyLabel) + updateHotkeyForm() +end + +function onSelectHotkeyLabel(hotkeyLabel) + currentHotkeyLabel = hotkeyLabel + updateHotkeyForm(true) +end + +function hotkeyCapture(assignWindow, keyCode, keyboardModifiers) + local keyCombo = determineKeyComboDesc(keyCode, keyboardModifiers) + local comboPreview = assignWindow:getChildById('comboPreview') + comboPreview:setText(tr('Current hotkey to add: %s', keyCombo)) + comboPreview.keyCombo = keyCombo + comboPreview:resizeToText() + assignWindow:getChildById('addButton'):enable() + return true +end + +function hotkeyCaptureOk(assignWindow, keyCombo) + addKeyCombo(keyCombo, nil, true) + assignWindow:destroy() +end diff --git a/800OTClient/modules/game_hotkeys/hotkeys_manager.otmod b/800OTClient/modules/game_hotkeys/hotkeys_manager.otmod new file mode 100644 index 0000000..4f4e0b7 --- /dev/null +++ b/800OTClient/modules/game_hotkeys/hotkeys_manager.otmod @@ -0,0 +1,9 @@ +Module + name: game_hotkeys + description: Manage client hotkeys + author: andrefaramir, BeniS + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ hotkeys_extra, hotkeys_manager ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_hotkeys/hotkeys_manager.otui b/800OTClient/modules/game_hotkeys/hotkeys_manager.otui new file mode 100644 index 0000000..c3025c7 --- /dev/null +++ b/800OTClient/modules/game_hotkeys/hotkeys_manager.otui @@ -0,0 +1,268 @@ +HotkeyListLabel < UILabel + font: verdana-11px-monochrome + background-color: alpha + text-offset: 2 0 + focusable: true + phantom: false + + $focus: + background-color: #ffffff22 + +MainWindow + id: hotkeysWindow + !text: tr('Hotkeys') + size: 370 475 + + @onEnter: modules.game_hotkeys.ok() + @onEscape: modules.game_hotkeys.cancel() + + ComboBox + id: configSelector + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + menu-scroll: true + menu-height: 125 + menu-scroll-step: 25 + text-offset: 5 2 + @onOptionChange: modules.game_hotkeys.onConfigChange() + @onSetup: | + self:addOption(tr("Hotkeys config #1")) + self:addOption(tr("Hotkeys config #2")) + self:addOption(tr("Hotkeys config #3")) + self:addOption(tr("Hotkeys config #4")) + self:addOption(tr("Hotkeys config #5")) + + + VerticalScrollBar + id: currentHotkeysScrollBar + height: 150 + anchors.top: prev.bottom + anchors.right: parent.right + margin-top: 5 + step: 14 + pixels-scroll: true + + TextList + id: currentHotkeys + vertical-scrollbar: currentHotkeysScrollBar + anchors.left: parent.left + anchors.right: prev.left + anchors.top: prev.top + anchors.bottom: prev.bottom + focusable: false + + Button + id: resetButton + width: 96 + !text: tr('Reset All') + anchors.left: parent.left + anchors.top: next.top + @onClick: modules.game_hotkeys.reset() + margin-right: 10 + + Button + id: addHotkeyButton + !text: tr('Add') + width: 64 + anchors.right: next.left + anchors.top: next.top + margin-right: 5 + @onClick: modules.game_hotkeys.addHotkey() + + Button + id: removeHotkeyButton + !text: tr('Remove') + width: 64 + enabled: false + anchors.right: parent.right + anchors.top: currentHotkeys.bottom + margin-top: 8 + @onClick: modules.game_hotkeys.removeHotkey() + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 5 + + Label + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 10 + !text: tr('Extra action:') + + ComboBox + id: action + anchors.left: prev.right + anchors.right: parent.right + anchors.top: prev.top + margin-left: 5 + margin-top: -4 + enabled: false + @onOptionChange: modules.game_hotkeys.updateHotkeyAction() + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 5 + + Label + id: hotKeyTextLabel + !text: tr('Edit hotkey text:') + enable: false + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 6 + + TextEdit + id: hotkeyText + enabled: false + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 2 + @onTextChange: modules.game_hotkeys.onHotkeyTextChange(self:getText()) + + CheckBox + id: sendAutomatically + !text: tr('Send automatically') + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + enabled:false + margin-top: 5 + @onCheckChange: modules.game_hotkeys.onSendAutomaticallyChange(self:isChecked()) + + Item + id: itemPreview + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 10 + virtual: true + + Button + id: selectObjectButton + !text: tr('Select object') + width: 128 + enabled: false + anchors.left: prev.right + anchors.top: prev.top + margin-left: 10 + @onClick: modules.game_hotkeys.startChooseItem() + + Button + id: clearObjectButton + !text: tr('Clear object') + width: 128 + enabled: false + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + margin-top: 2 + @onClick: modules.game_hotkeys.clearObject() + + ButtonBox + id: useOnSelf + !text: tr('Use on yourself') + width: 128 + enabled: false + anchors.left: selectObjectButton.right + anchors.right: parent.right + anchors.top: selectObjectButton.top + checked: false + margin-left: 10 + + ButtonBox + id: useOnTarget + !text: tr('Use on target') + width: 128 + enabled: false + anchors.left: prev.left + anchors.right: parent.right + anchors.top: prev.bottom + checked: false + margin-top: 2 + + ButtonBox + id: useWith + !text: tr('With crosshair') + width: 128 + enabled: false + anchors.left: prev.left + anchors.right: parent.right + anchors.top: prev.bottom + checked: false + margin-top: 2 + + HorizontalSeparator + id: separator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + margin-bottom: 5 + + Button + id: okButton + !text: tr('Ok') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + @onClick: modules.game_hotkeys.ok() + margin-right: 10 + + Button + id: cancelButton + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: modules.game_hotkeys.cancel() + +HotkeyAssignWindow < MainWindow + id: assignWindow + !text: tr('Button Assign') + size: 360 150 + @onEscape: self:destroy() + + Label + !text: tr('Please, press the key you wish to add onto your hotkeys manager') + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-auto-resize: true + text-align: left + + Label + id: comboPreview + !text: tr('Current hotkey to add: %s', 'none') + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: prev.bottom + margin-top: 10 + text-auto-resize: true + + HorizontalSeparator + id: separator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + margin-bottom: 10 + + Button + id: addButton + !text: tr('Add') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + @onClick: modules.game_hotkeys.hotkeyCaptureOk(self:getParent(), self:getParent():getChildById('comboPreview').keyCombo) + + Button + id: cancelButton + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: self:getParent():destroy() diff --git a/800OTClient/modules/game_imbuing/imbuing.lua b/800OTClient/modules/game_imbuing/imbuing.lua new file mode 100644 index 0000000..a4704c6 --- /dev/null +++ b/800OTClient/modules/game_imbuing/imbuing.lua @@ -0,0 +1,313 @@ +local imbuingWindow +local bankGold = 0 +local inventoryGold = 0 +local itemImbuements = {} +local emptyImbue +local groupsCombo +local imbueLevelsCombo +local protectionBtn +local clearImbue +local selectedImbue +local imbueItems = {} +local protection = false +local clearConfirmWindow +local imbueConfirmWindow + +function init() + connect(g_game, { + onGameEnd = hide, + onResourceBalance = onResourceBalance, + onImbuementWindow = onImbuementWindow, + onCloseImbuementWindow = onCloseImbuementWindow + }) + + imbuingWindow = g_ui.displayUI('imbuing') + emptyImbue = imbuingWindow.emptyImbue + groupsCombo = emptyImbue.groups + imbueLevelsCombo = emptyImbue.imbuement + protectionBtn = emptyImbue.protection + clearImbue = imbuingWindow.clearImbue + imbuingWindow:hide() + + groupsCombo.onOptionChange = function(widget) + imbueLevelsCombo:clear() + if itemImbuements ~= nil then + local selectedGroup = groupsCombo:getCurrentOption().text + for _,imbuement in ipairs(itemImbuements) do + if imbuement["group"] == selectedGroup then + emptyImbue.imbuement:addOption(imbuement["name"]) + end + end + imbueLevelsCombo.onOptionChange(imbueLevelsCombo) -- update options + end + end + + imbueLevelsCombo.onOptionChange = function(widget) + setProtection(false) + local selectedGroup = groupsCombo:getCurrentOption().text + for _,imbuement in ipairs(itemImbuements) do + if imbuement["group"] == selectedGroup then + if #imbuement["sources"] == widget.currentIndex then + selectedImbue = imbuement + for i,source in ipairs(imbuement["sources"]) do + for _,item in ipairs(imbueItems) do + if item:getId() == source["item"]:getId() then + if item:getCount() >= source["item"]:getCount() then + emptyImbue.imbue:setImageSource("/images/game/imbuing/imbue_green") + emptyImbue.imbue:setEnabled(true) + emptyImbue.requiredItems:getChildByIndex(i).count:setColor("white") + end + if item:getCount() < source["item"]:getCount() then + emptyImbue.imbue:setEnabled(false) + emptyImbue.imbue:setImageSource("/images/game/imbuing/imbue_empty") + emptyImbue.requiredItems:getChildByIndex(i).count:setColor("red") + end + emptyImbue.requiredItems:getChildByIndex(i).count:setText(item:getCount() .. "/" .. source["item"]:getCount()) + end + end + emptyImbue.requiredItems:getChildByIndex(i).item:setItemId(source["item"]:getId()) + emptyImbue.requiredItems:getChildByIndex(i).item:setTooltip("The imbuement requires " .. source["description"] .. ".") + end + for i = 3, widget.currentIndex + 1, -1 do + emptyImbue.requiredItems:getChildByIndex(i).count:setText("") + emptyImbue.requiredItems:getChildByIndex(i).item:setItemId(0) + emptyImbue.requiredItems:getChildByIndex(i).item:setTooltip("") + end + emptyImbue.protectionCost:setText(imbuement["protectionCost"]) + emptyImbue.cost:setText(imbuement["cost"]) + if not protection and (bankGold + inventoryGold) < imbuement["cost"] then + emptyImbue.imbue:setEnabled(false) + emptyImbue.imbue:setImageSource("/images/game/imbuing/imbue_empty") + emptyImbue.cost:setColor("red") + end + if not protection and (bankGold + inventoryGold) >= imbuement["cost"] then + emptyImbue.cost:setColor("white") + end + if protection and (bankGold + inventoryGold) < (imbuement["cost"] + imbuement["protectionCost"]) then + emptyImbue.imbue:setEnabled(false) + emptyImbue.imbue:setImageSource("/images/game/imbuing/imbue_empty") + emptyImbue.cost:setColor("red") + end + if protection and (bankGold + inventoryGold) >= (imbuement["cost"] + imbuement["protectionCost"]) then + emptyImbue.cost:setColor("white") + end + emptyImbue.successRate:setText(imbuement["successRate"] .. "%") + if selectedImbue["successRate"] > 50 then + emptyImbue.successRate:setColor("white") + else + emptyImbue.successRate:setColor("red") + end + emptyImbue.description:setText(imbuement["description"]) + end + end + end + end + + protectionBtn.onClick = function() + setProtection(not protection) + end +end + +function setProtection(value) + protection = value + if protection then + emptyImbue.cost:setText(selectedImbue["cost"] + selectedImbue["protectionCost"]) + emptyImbue.successRate:setText("100%") + emptyImbue.successRate:setColor("green") + protectionBtn:setImageClip(torect("66 0 66 66")) + else + if selectedImbue then + emptyImbue.cost:setText(selectedImbue["cost"]) + emptyImbue.successRate:setText(selectedImbue["successRate"] .. "%") + if selectedImbue["successRate"] > 50 then + emptyImbue.successRate:setColor("white") + else + emptyImbue.successRate:setColor("red") + end + end + protectionBtn:setImageClip(torect("0 0 66 66")) + end +end + +function terminate() + disconnect(g_game, { + onGameEnd = hide, + onResourceBalance = onResourceBalance, + onImbuementWindow = onImbuementWindow, + onCloseImbuementWindow = onCloseImbuementWindow + }) + + imbuingWindow:destroy() +end + +function resetSlots() + emptyImbue:setVisible(false) + clearImbue:setVisible(false) + for i=1,3 do + imbuingWindow.itemInfo.slots:getChildByIndex(i):setText("Slot " .. i) + imbuingWindow.itemInfo.slots:getChildByIndex(i):setEnabled(false) + imbuingWindow.itemInfo.slots:getChildByIndex(i):setTooltip("Items can have up to three imbuements slots. This slot is not available for this item.") + imbuingWindow.itemInfo.slots:getChildByIndex(i).onClick = nil + end +end + +function selectSlot(widget, slotId, activeSlot) + if activeSlot then + emptyImbue:setVisible(false) + widget:setText(activeSlot[1]["name"]) + clearImbue.title:setText('Clear Imbuement "' .. activeSlot[1]["name"] .. '"') + clearImbue.groups:clear() + clearImbue.groups:addOption(activeSlot[1]["group"]) + clearImbue.imbuement:clear() + clearImbue.imbuement:addOption(activeSlot[1]["name"]) + clearImbue.description:setText(activeSlot[1]["description"]) + + hours = string.format("%02.f", math.floor(activeSlot[2]/3600)) + mins = string.format("%02.f", math.floor(activeSlot[2]/60 - (hours*60))) + clearImbue.time.timeRemaining:setText(hours..":"..mins.."h") + + clearImbue.cost:setText(activeSlot[3]) + if (bankGold + inventoryGold) < activeSlot[3] then + emptyImbue.clear:setEnabled(false) + emptyImbue.clear:setImageSource("/images/game/imbuing/imbue_empty") + emptyImbue.cost:setColor("red") + end + + local yesCallback = function() + g_game.clearImbuement(slotId) + widget:setText("Slot " .. (slotId + 1)) + if clearConfirmWindow then + clearConfirmWindow:destroy() + clearConfirmWindow=nil + end + end + local noCallback = function() + imbuingWindow:show() + if clearConfirmWindow then + clearConfirmWindow:destroy() + clearConfirmWindow=nil + end + end + + clearImbue.clear.onClick = function() + imbuingWindow:hide() + clearConfirmWindow = displayGeneralBox(tr('Confirm Clearing'), tr('Do you wish to spend ' .. activeSlot[3] .. ' gold coins to clear the imbuement "' .. activeSlot[1]["name"] .. '" from your item?'), { + { text=tr('Yes'), callback=yesCallback }, + { text=tr('No'), callback=noCallback }, + anchor=AnchorHorizontalCenter}, yesCallback, noCallback) + end + + clearImbue:setVisible(true) + else + emptyImbue:setVisible(true) + clearImbue:setVisible(false) + + local yesCallback = function() + g_game.applyImbuement(slotId, selectedImbue["id"], protection) + if clearConfirmWindow then + clearConfirmWindow:destroy() + clearConfirmWindow=nil + end + widget:setText(selectedImbue["name"]) + imbuingWindow:show() + end + local noCallback = function() + imbuingWindow:show() + if clearConfirmWindow then + clearConfirmWindow:destroy() + clearConfirmWindow=nil + end + end + + emptyImbue.imbue.onClick = function() + imbuingWindow:hide() + local cost = selectedImbue["cost"] + local successRate = selectedImbue["successRate"] + if protection then + cost = cost + selectedImbue["protectionCost"] + successRate = "100" + end + clearConfirmWindow = displayGeneralBox(tr('Confirm Imbuing Attempt'), 'You are about to imbue your item with "' .. selectedImbue["name"] .. '".\nYour chance to succeed is ' .. successRate .. '%. It will consume the required astral sources and '.. cost ..' gold coins.\nDo you wish to proceed?', { + { text=tr('Yes'), callback=yesCallback }, + { text=tr('No'), callback=noCallback }, + anchor=AnchorHorizontalCenter}, yesCallback, noCallback) + end + end +end + +function onImbuementWindow(itemId, slots, activeSlots, imbuements, needItems) + if not itemId then + return + end + resetSlots() + imbueItems = table.copy(needItems) + imbuingWindow.itemInfo.item:setItemId(itemId) + + for i=1, slots do + local slot = imbuingWindow.itemInfo.slots:getChildByIndex(i) + slot.onClick = function(widget) + selectSlot(widget, i - 1) + end + slot:setTooltip("Use this slot to imbue your item. Depending on the item you can have up to three different imbuements.") + slot:setEnabled(true) + + if slot:getId() == "slot0" then + selectSlot(slot, i - 1) + end + end + + for i, slot in pairs(activeSlots) do + local activeSlotBtn = imbuingWindow.itemInfo.slots:getChildById("slot" .. i) + activeSlotBtn.onClick = function(widget) + selectSlot(widget, i, slot) + end + if activeSlotBtn:getId() == "slot0" then + selectSlot(activeSlotBtn, i, slot) + end + end + + if imbuements ~= nil then + groupsCombo:clear() + imbueLevelsCombo:clear() + itemImbuements = table.copy(imbuements) + for _,imbuement in ipairs(itemImbuements) do + if not groupsCombo:isOption(imbuement["group"]) then + groupsCombo:addOption(imbuement["group"]) + end + end + end + show() +end + +function onResourceBalance(type, balance) + if type == 0 then + bankGold = balance + elseif type == 1 then + inventoryGold = balance + end + if type == 0 or type == 1 then + imbuingWindow.balance:setText(tr("Balance") .. ":\n" .. (bankGold + inventoryGold)) + end +end + +function onCloseImbuementWindow() + resetSlots() +end + +function hide() + g_game.closeImbuingWindow() + imbuingWindow:hide() +end + +function show() + imbuingWindow:show() + imbuingWindow:raise() + imbuingWindow:focus() +end + +function toggle() + if imbuingWindow:isVisible() then + return hide() + end + show() +end \ No newline at end of file diff --git a/800OTClient/modules/game_imbuing/imbuing.otmod b/800OTClient/modules/game_imbuing/imbuing.otmod new file mode 100644 index 0000000..036a849 --- /dev/null +++ b/800OTClient/modules/game_imbuing/imbuing.otmod @@ -0,0 +1,9 @@ +Module + name: game_imbuing + description: imbuing + author: Vincent#1766 on discord + website: http://otclient.ovh + sandboxed: true + scripts: [ imbuing ] + @onLoad: init() + @onUnload: terminate() \ No newline at end of file diff --git a/800OTClient/modules/game_imbuing/imbuing.otui b/800OTClient/modules/game_imbuing/imbuing.otui new file mode 100644 index 0000000..9ae7505 --- /dev/null +++ b/800OTClient/modules/game_imbuing/imbuing.otui @@ -0,0 +1,346 @@ +Slot < Button + width: 70 + height: 66 + anchors.verticalCenter: parent.verticalCenter + enabled: false + text-wrap: true + !tooltip: tr('Items can have up to three imbuements slots. This slot is not available for this item.') + +RequiredItem < Panel + width: 66 + height: 90 + + UIItem + id: item + height: 66 + width: 66 + anchors.left: parent.left + anchors.top: parent.top + + FlatLabel + id: count + margin-top: 5 + text-align: center + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + +ItemInformation < Panel + height: 100 + border: 1 black + padding: 5 + + Label + id: title + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-align: center + !text: tr("Item Information") + + UIItem + id: item + anchors.top: parent.top + anchors.left: parent.left + anchors.VerticalCenter: parent.VerticalCenter + margin-left: 10 + margin-top: 5 + height: 64 + width: 64 + + Panel + id: slots + width: 240 + height: 66 + margin-top: 5 + anchors.VerticalCenter: parent.VerticalCenter + anchors.top: prev.top + anchors.right: parent.right + + Slot + id: slot0 + !text: tr("Slot 1") + text-align: center + anchors.left: parent.left + + Slot + id: slot1 + !text: tr("Slot 2") + text-align: center + margin-left: 10 + anchors.left: prev.right + + Slot + id: slot2 + !text: tr("Slot 3") + text-align: center + margin-left: 10 + anchors.left: prev.right + + Label + id: selectSlot + margin-right: 15 + anchors.right: slots.left + anchors.VerticalCenter: parent.VerticalCenter + !text: tr("Select Slot:") + +EmptyImbue < Panel + height: 240 + border: 1 black + padding: 5 + + Label + id: title + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-align: center + !text: tr("Imbue Empty Slot") + + ComboBox + id: groups + anchors.top: title.bottom + anchors.left: parent.left + anchors.right: parent.HorizontalCenter + margin-right: 3 + margin-top: 5 + isdisabled: true + + ComboBox + id: imbuement + anchors.top: prev.top + anchors.left: groups.right + anchors.right: parent.right + margin-left: 3 + + Label + id: description + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 10 + height: 85 + + Label + id: info + anchors.bottom: prev.bottom + anchors.left: parent.left + !text: tr('Requires the following astral sources:') + + Label + id: successRate + anchors.top: info.top + anchors.right: parent.right + width: 35 + text-align: right + + Label + id: successRateTitle + anchors.top: info.top + anchors.right: successRate.left + margin-right: 15 + !text: tr('Success Rate:') + + Panel + id: requiredItems + width: 210 + height: 90 + anchors.left: parent.left + anchors.bottom: parent.bottom + + RequiredItem + id: item1 + anchors.left: parent.left + + RequiredItem + id: item2 + margin-left: 10 + anchors.left: prev.right + + RequiredItem + id: item3 + margin-left: 10 + anchors.left: prev.right + + UIButton + id: protection + width: 66 + height: 66 + anchors.top: prev.top + anchors.left: prev.right + image-source: /images/game/imbuing/100percent + image-clip: 0 0 66 66 + margin-left: 15 + !tooltip: ("Bribe the fates! Click here to raise your chance to 100%. For guaranteed success use gold.") + + FlatLabel + id: protectionCost + margin-top: 5 + text-align: center + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + + UIWidget + id: horizontalArrow + anchors.left: prev.right + anchors.verticalCenter: requiredItems.verticalCenter + margin-left: 45 + width: 12 + height: 21 + image-source: /images/ui/arrow_horizontal + image-rect: 0 0 12 21 + image-clip: 12 0 12 21 + phantom: false + + UIButton + id: imbue + width: 128 + height: 66 + anchors.top: requiredItems.top + anchors.right: parent.right + image-source: /images/game/imbuing/imbue_empty + image-clip: 0 0 128 66 + margin-left: 15 + !tooltip: tr("Click here to carry out the selected imbuement. This will consume the required astral sources and gold.") + + $pressed: + image-clip: 0 66 128 66 + + FlatLabel + id: cost + margin-top: 5 + text-align: center + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + +ClearImbue < Panel + height: 240 + border: 1 black + padding: 5 + + Label + id: title + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-align: center + !text: tr("Clear Imbuement") + + ComboBox + id: groups + anchors.top: title.bottom + anchors.left: parent.left + anchors.right: parent.HorizontalCenter + margin-right: 3 + margin-top: 5 + enabled: false + + ComboBox + id: imbuement + anchors.top: prev.top + anchors.left: groups.right + anchors.right: parent.right + margin-left: 3 + enabled: false + + Label + id: description + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 10 + height: 85 + + Label + id: info + anchors.bottom: prev.bottom + anchors.left: parent.left + !text: tr('Time remaining:') + + Label + id: clearImbuementTitle + anchors.top: info.top + anchors.right: parent.right + !text: tr('Clear imbuement:') + + Panel + id: time + width: 210 + height: 90 + anchors.left: parent.left + anchors.bottom: parent.bottom + + FlatLabel + id: timeRemaining + size: 86 25 + margin-bottom: 20 + text-align: center + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + + UIButton + id: clear + width: 128 + height: 66 + anchors.top: time.top + anchors.right: parent.right + image-source: /images/game/imbuing/clear + image-clip: 0 0 128 66 + margin-left: 15 + !tooltip: tr("Your needs have changed? Click here to clear the imbuement from your item for a fee.") + + $pressed: + image-clip: 0 66 128 66 + + FlatLabel + id: cost + margin-top: 5 + text-align: center + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + + +MainWindow + id: imbuingWindow + !text: tr('Imbue Item') + size: 550 430 + background-color: #AAAAAA + @onEscape: modules.game_imbuing.hide() + + ItemInformation + id: itemInfo + anchors.left: parent.left + anchors.top: parent.top + anchors.right: parent.right + + EmptyImbue + id: emptyImbue + anchors.left: parent.left + anchors.top: prev.bottom + anchors.right: parent.right + margin-top: 5 + + ClearImbue + id: clearImbue + anchors.left: parent.left + anchors.top: emptyImbue.top + anchors.right: parent.right + + Button + id: close + !text: tr('Close') + width: 50 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: modules.game_imbuing.hide() + + Label + id: balance + height: 25 + anchors.right: prev.left + anchors.left: parent.left + anchors.bottom: parent.bottom diff --git a/800OTClient/modules/game_interface/gameinterface.lua b/800OTClient/modules/game_interface/gameinterface.lua new file mode 100644 index 0000000..3b88522 --- /dev/null +++ b/800OTClient/modules/game_interface/gameinterface.lua @@ -0,0 +1,1188 @@ +gameRootPanel = nil +gameMapPanel = nil +gameRightPanels = nil +gameLeftPanels = nil +gameBottomPanel = nil +gameBottomActionPanel = nil +gameLeftActionPanel = nil +gameRightActionPanel = nil +gameLeftActions = nil +gameTopBar = nil +logoutButton = nil +mouseGrabberWidget = nil +countWindow = nil +logoutWindow = nil +exitWindow = nil +bottomSplitter = nil +limitedZoom = false +hookedMenuOptions = {} +lastDirTime = g_clock.millis() + +function init() + g_ui.importStyle('styles/countwindow') + + connect(g_game, { + onGameStart = onGameStart, + onGameEnd = onGameEnd, + onLoginAdvice = onLoginAdvice, + }, true) + + -- Call load AFTER game window has been created and + -- resized to a stable state, otherwise the saved + -- settings can get overridden by false onGeometryChange + -- events + connect(g_app, { + onRun = load, + onExit = save + }) + + gameRootPanel = g_ui.displayUI('gameinterface') + gameRootPanel:hide() + gameRootPanel:lower() + gameRootPanel.onGeometryChange = updateStretchShrink + + mouseGrabberWidget = gameRootPanel:getChildById('mouseGrabber') + mouseGrabberWidget.onMouseRelease = onMouseGrabberRelease + mouseGrabberWidget.onTouchRelease = mouseGrabberWidget.onMouseRelease + + bottomSplitter = gameRootPanel:getChildById('bottomSplitter') + gameMapPanel = gameRootPanel:getChildById('gameMapPanel') + gameRightPanels = gameRootPanel:getChildById('gameRightPanels') + gameLeftPanels = gameRootPanel:getChildById('gameLeftPanels') + gameBottomPanel = gameRootPanel:getChildById('gameBottomPanel') + gameBottomActionPanel = gameRootPanel:getChildById('gameBottomActionPanel') + gameRightActionPanel = gameRootPanel:getChildById('gameRightActionPanel') + gameLeftActionPanel = gameRootPanel:getChildById('gameLeftActionPanel') + gameTopBar = gameRootPanel:getChildById('gameTopBar') + gameLeftActions = gameRootPanel:getChildById('gameLeftActions') + connect(gameLeftPanel, { onVisibilityChange = onLeftPanelVisibilityChange }) + + logoutButton = modules.client_topmenu.addLeftButton('logoutButton', tr('Exit'), + '/images/topbuttons/logout', tryLogout, true) + + + gameRightPanels:addChild(g_ui.createWidget('GameSidePanel')) + + setupLeftActions() + refreshViewMode() + + bindKeys() + + connect(gameMapPanel, { onGeometryChange = updateSize, onVisibleDimensionChange = updateSize }) + connect(g_game, { onMapChangeAwareRange = updateSize }) + + if g_game.isOnline() then + show() + end +end + +function bindKeys() + gameRootPanel:setAutoRepeatDelay(10) + + local lastAction = 0 + g_keyboard.bindKeyPress('Escape', function() + if lastAction + 50 > g_clock.millis() then return end + lastAction = g_clock.millis() + g_game.cancelAttackAndFollow() + end, gameRootPanel) + g_keyboard.bindKeyPress('Ctrl+=', function() if g_game.getFeature(GameNoDebug) then return end gameMapPanel:zoomIn() end, gameRootPanel) + g_keyboard.bindKeyPress('Ctrl+-', function() if g_game.getFeature(GameNoDebug) then return end gameMapPanel:zoomOut() end, gameRootPanel) + g_keyboard.bindKeyDown('Ctrl+Q', function() tryLogout(false) end, gameRootPanel) + g_keyboard.bindKeyDown('Ctrl+L', function() tryLogout(false) end, gameRootPanel) + g_keyboard.bindKeyDown('Ctrl+W', function() g_map.cleanTexts() modules.game_textmessage.clearMessages() end, gameRootPanel) +end + +function terminate() + hide() + + hookedMenuOptions = {} + markThing = nil + + + disconnect(g_game, { + onGameStart = onGameStart, + onGameEnd = onGameEnd, + onLoginAdvice = onLoginAdvice + }) + + disconnect(gameMapPanel, { onGeometryChange = updateSize }) + connect(gameMapPanel, { onGeometryChange = updateSize, onVisibleDimensionChange = updateSize }) + + logoutButton:destroy() + gameRootPanel:destroy() +end + +function onGameStart() + refreshViewMode() + show() + + -- open tibia has delay in auto walking + if not g_game.isOfficialTibia() then + g_game.enableFeature(GameForceFirstAutoWalkStep) + else + g_game.disableFeature(GameForceFirstAutoWalkStep) + end +end + +function onGameEnd() + hide() + modules.client_topmenu.getTopMenu():setImageColor('white') +end + +function show() + connect(g_app, { onClose = tryExit }) + modules.client_background.hide() + gameRootPanel:show() + gameRootPanel:focus() + gameMapPanel:followCreature(g_game.getLocalPlayer()) + + updateStretchShrink() + logoutButton:setTooltip(tr('Logout')) + + addEvent(function() + if not limitedZoom or g_game.isGM() then + gameMapPanel:setMaxZoomOut(513) + gameMapPanel:setLimitVisibleRange(false) + else + gameMapPanel:setMaxZoomOut(15) + gameMapPanel:setLimitVisibleRange(true) + end + end) +end + +function hide() + disconnect(g_app, { onClose = tryExit }) + logoutButton:setTooltip(tr('Exit')) + + if logoutWindow then + logoutWindow:destroy() + logoutWindow = nil + end + if exitWindow then + exitWindow:destroy() + exitWindow = nil + end + if countWindow then + countWindow:destroy() + countWindow = nil + end + gameRootPanel:hide() + gameMapPanel:setShader("") + modules.client_background.show() +end + +function save() + local settings = {} + settings.splitterMarginBottom = bottomSplitter:getMarginBottom() + g_settings.setNode('game_interface', settings) +end + +function load() + local settings = g_settings.getNode('game_interface') + if settings then + if settings.splitterMarginBottom then + bottomSplitter:setMarginBottom(settings.splitterMarginBottom) + end + end +end + +function onLoginAdvice(message) + displayInfoBox(tr("For Your Information"), message) +end + +function forceExit() + g_game.cancelLogin() + scheduleEvent(exit, 10) + return true +end + +function tryExit() + if exitWindow then + return true + end + + local exitFunc = function() g_game.safeLogout() forceExit() end + local logoutFunc = function() g_game.safeLogout() exitWindow:destroy() exitWindow = nil end + local cancelFunc = function() exitWindow:destroy() exitWindow = nil end + + exitWindow = displayGeneralBox(tr('Exit'), tr("If you shut down the program, your character might stay in the game.\nClick on 'Logout' to ensure that you character leaves the game properly.\nClick on 'Exit' if you want to exit the program without logging out your character."), + { { text=tr('Force Exit'), callback=exitFunc }, + { text=tr('Logout'), callback=logoutFunc }, + { text=tr('Cancel'), callback=cancelFunc }, + anchor=AnchorHorizontalCenter }, logoutFunc, cancelFunc) + + return true +end + +function tryLogout(prompt) + if type(prompt) ~= "boolean" then + prompt = true + end + if not g_game.isOnline() then + exit() + return + end + + if logoutWindow then + return + end + + local msg, yesCallback + if not g_game.isConnectionOk() then + msg = 'Your connection is failing, if you logout now your character will be still online, do you want to force logout?' + + yesCallback = function() + g_game.forceLogout() + if logoutWindow then + logoutWindow:destroy() + logoutWindow=nil + end + end + else + msg = 'Are you sure you want to logout?' + + yesCallback = function() + g_game.safeLogout() + if logoutWindow then + logoutWindow:destroy() + logoutWindow=nil + end + end + end + + local noCallback = function() + logoutWindow:destroy() + logoutWindow=nil + end + + if prompt then + logoutWindow = displayGeneralBox(tr('Logout'), tr(msg), { + { text=tr('Yes'), callback=yesCallback }, + { text=tr('No'), callback=noCallback }, + anchor=AnchorHorizontalCenter}, yesCallback, noCallback) + else + yesCallback() + end +end + +function updateStretchShrink() + if modules.client_options.getOption('dontStretchShrink') and not alternativeView then + gameMapPanel:setVisibleDimension({ width = 15, height = 11 }) + + -- Set gameMapPanel size to height = 11 * 32 + 2 + bottomSplitter:setMarginBottom(bottomSplitter:getMarginBottom() + (gameMapPanel:getHeight() - 32 * 11) - 10) + end +end + +function onMouseGrabberRelease(self, mousePosition, mouseButton) + if mouseButton == MouseTouch then return end + if selectedThing == nil then return false end + if mouseButton == MouseLeftButton then + local clickedWidget = gameRootPanel:recursiveGetChildByPos(mousePosition, false) + if clickedWidget then + if selectedType == 'use' then + onUseWith(clickedWidget, mousePosition) + elseif selectedType == 'trade' then + onTradeWith(clickedWidget, mousePosition) + end + end + end + + selectedThing = nil + g_mouse.popCursor('target') + self:ungrabMouse() + gameMapPanel:blockNextMouseRelease(true) + return true +end + +function onUseWith(clickedWidget, mousePosition) + if clickedWidget:getClassName() == 'UIGameMap' then + local tile = clickedWidget:getTile(mousePosition) + if tile then + if selectedThing:isFluidContainer() or selectedThing:isMultiUse() then + if selectedThing:getId() == 3180 or selectedThing:getId() == 3156 then + -- special version for mwall + g_game.useWith(selectedThing, tile:getTopUseThing(), selectedSubtype) + else + g_game.useWith(selectedThing, tile:getTopMultiUseThingEx(clickedWidget:getPositionOffset(mousePosition)), selectedSubtype) + end + else + g_game.useWith(selectedThing, tile:getTopUseThing(), selectedSubtype) + end + end + elseif clickedWidget:getClassName() == 'UIItem' and not clickedWidget:isVirtual() then + g_game.useWith(selectedThing, clickedWidget:getItem(), selectedSubtype) + elseif clickedWidget:getClassName() == 'UICreatureButton' then + local creature = clickedWidget:getCreature() + if creature then + g_game.useWith(selectedThing, creature, selectedSubtype) + end + end +end + +function onTradeWith(clickedWidget, mousePosition) + if clickedWidget:getClassName() == 'UIGameMap' then + local tile = clickedWidget:getTile(mousePosition) + if tile then + g_game.requestTrade(selectedThing, tile:getTopCreatureEx(clickedWidget:getPositionOffset(mousePosition))) + end + elseif clickedWidget:getClassName() == 'UICreatureButton' then + local creature = clickedWidget:getCreature() + if creature then + g_game.requestTrade(selectedThing, creature) + end + end +end + +function startUseWith(thing, subType) + gameMapPanel:blockNextMouseRelease() + if not thing then return end + if g_ui.isMouseGrabbed() then + if selectedThing then + selectedThing = thing + selectedType = 'use' + end + return + end + selectedType = 'use' + selectedThing = thing + selectedSubtype = subType or 0 + mouseGrabberWidget:grabMouse() + g_mouse.pushCursor('target') +end + +function startTradeWith(thing) + if not thing then return end + if g_ui.isMouseGrabbed() then + if selectedThing then + selectedThing = thing + selectedType = 'trade' + end + return + end + selectedType = 'trade' + selectedThing = thing + mouseGrabberWidget:grabMouse() + g_mouse.pushCursor('target') +end + +function isMenuHookCategoryEmpty(category) + if category then + for _,opt in pairs(category) do + if opt then return false end + end + end + return true +end + +function addMenuHook(category, name, callback, condition, shortcut) + if not hookedMenuOptions[category] then + hookedMenuOptions[category] = {} + end + hookedMenuOptions[category][name] = { + callback = callback, + condition = condition, + shortcut = shortcut + } +end + +function removeMenuHook(category, name) + if not name then + hookedMenuOptions[category] = {} + else + hookedMenuOptions[category][name] = nil + end +end + +function createThingMenu(menuPosition, lookThing, useThing, creatureThing) + if not g_game.isOnline() then return end + + local menu = g_ui.createWidget('PopupMenu') + menu:setGameMenu(true) + + local classic = modules.client_options.getOption('classicControl') + local shortcut = nil + + if not classic and not g_app.isMobile() then shortcut = '(Shift)' else shortcut = nil end + if lookThing then + menu:addOption(tr('Look'), function() g_game.look(lookThing) end, shortcut) + end + + if not classic and not g_app.isMobile() then shortcut = '(Ctrl)' else shortcut = nil end + if useThing then + if useThing:isContainer() then + if useThing:getParentContainer() then + menu:addOption(tr('Open'), function() g_game.open(useThing, useThing:getParentContainer()) end, shortcut) + menu:addOption(tr('Open in new window'), function() g_game.open(useThing) end) + else + menu:addOption(tr('Open'), function() g_game.open(useThing) end, shortcut) + end + else + if useThing:isMultiUse() then + menu:addOption(tr('Use with ...'), function() startUseWith(useThing) end, shortcut) + else + menu:addOption(tr('Use'), function() g_game.use(useThing) end, shortcut) + end + end + + if useThing:isRotateable() then + menu:addOption(tr('Rotate'), function() g_game.rotate(useThing) end) + end + if useThing:isWrapable() then + menu:addOption(tr('Wrap'), function() g_game.wrap(useThing) end) + end + if useThing:isUnwrapable() then + menu:addOption(tr('Unwrap'), function() g_game.wrap(useThing) end) + end + + if g_game.getFeature(GameBrowseField) and useThing:getPosition().x ~= 0xffff then + menu:addOption(tr('Browse Field'), function() g_game.browseField(useThing:getPosition()) end) + end + end + + if lookThing and not lookThing:isCreature() and not lookThing:isNotMoveable() and lookThing:isPickupable() then + menu:addSeparator() + menu:addOption(tr('Trade with ...'), function() startTradeWith(lookThing) end) + end + + if lookThing then + local parentContainer = lookThing:getParentContainer() + if parentContainer and parentContainer:hasParent() then + menu:addOption(tr('Move up'), function() g_game.moveToParentContainer(lookThing, lookThing:getCount()) end) + end + end + + if creatureThing then + local localPlayer = g_game.getLocalPlayer() + menu:addSeparator() + + if creatureThing:isLocalPlayer() then + menu:addOption(tr('Set Outfit'), function() g_game.requestOutfit() end) + + if g_game.getFeature(GamePlayerMounts) then + if not localPlayer:isMounted() then + menu:addOption(tr('Mount'), function() localPlayer:mount() end) + else + menu:addOption(tr('Dismount'), function() localPlayer:dismount() end) + end + end + + if g_game.getFeature(GamePrey) and modules.game_prey then + menu:addOption(tr('Open Prey Dialog'), function() modules.game_prey.show() end) + end + + if creatureThing:isPartyMember() then + if creatureThing:isPartyLeader() then + if creatureThing:isPartySharedExperienceActive() then + menu:addOption(tr('Disable Shared Experience'), function() g_game.partyShareExperience(false) end) + else + menu:addOption(tr('Enable Shared Experience'), function() g_game.partyShareExperience(true) end) + end + end + menu:addOption(tr('Leave Party'), function() g_game.partyLeave() end) + end + + else + local localPosition = localPlayer:getPosition() + if not classic and not g_app.isMobile() then shortcut = '(Alt)' else shortcut = nil end + if creatureThing:getPosition().z == localPosition.z then + if g_game.getAttackingCreature() ~= creatureThing then + menu:addOption(tr('Attack'), function() g_game.attack(creatureThing) end, shortcut) + else + menu:addOption(tr('Stop Attack'), function() g_game.cancelAttack() end, shortcut) + end + + if g_game.getFollowingCreature() ~= creatureThing then + menu:addOption(tr('Follow'), function() g_game.follow(creatureThing) end) + else + menu:addOption(tr('Stop Follow'), function() g_game.cancelFollow() end) + end + end + + if creatureThing:isPlayer() then + menu:addSeparator() + local creatureName = creatureThing:getName() + menu:addOption(tr('Message to %s', creatureName), function() g_game.openPrivateChannel(creatureName) end) + if modules.game_console.getOwnPrivateTab() then + menu:addOption(tr('Invite to private chat'), function() g_game.inviteToOwnChannel(creatureName) end) + menu:addOption(tr('Exclude from private chat'), function() g_game.excludeFromOwnChannel(creatureName) end) -- [TODO] must be removed after message's popup labels been implemented + end + if not localPlayer:hasVip(creatureName) then + menu:addOption(tr('Add to VIP list'), function() g_game.addVip(creatureName) end) + end + + if modules.game_console.isIgnored(creatureName) then + menu:addOption(tr('Unignore') .. ' ' .. creatureName, function() modules.game_console.removeIgnoredPlayer(creatureName) end) + else + menu:addOption(tr('Ignore') .. ' ' .. creatureName, function() modules.game_console.addIgnoredPlayer(creatureName) end) + end + + local localPlayerShield = localPlayer:getShield() + local creatureShield = creatureThing:getShield() + + if localPlayerShield == ShieldNone or localPlayerShield == ShieldWhiteBlue then + if creatureShield == ShieldWhiteYellow then + menu:addOption(tr('Join %s\'s Party', creatureThing:getName()), function() g_game.partyJoin(creatureThing:getId()) end) + else + menu:addOption(tr('Invite to Party'), function() g_game.partyInvite(creatureThing:getId()) end) + end + elseif localPlayerShield == ShieldWhiteYellow then + if creatureShield == ShieldWhiteBlue then + menu:addOption(tr('Revoke %s\'s Invitation', creatureThing:getName()), function() g_game.partyRevokeInvitation(creatureThing:getId()) end) + end + elseif localPlayerShield == ShieldYellow or localPlayerShield == ShieldYellowSharedExp or localPlayerShield == ShieldYellowNoSharedExpBlink or localPlayerShield == ShieldYellowNoSharedExp then + if creatureShield == ShieldWhiteBlue then + menu:addOption(tr('Revoke %s\'s Invitation', creatureThing:getName()), function() g_game.partyRevokeInvitation(creatureThing:getId()) end) + elseif creatureShield == ShieldBlue or creatureShield == ShieldBlueSharedExp or creatureShield == ShieldBlueNoSharedExpBlink or creatureShield == ShieldBlueNoSharedExp then + menu:addOption(tr('Pass Leadership to %s', creatureThing:getName()), function() g_game.partyPassLeadership(creatureThing:getId()) end) + else + menu:addOption(tr('Invite to Party'), function() g_game.partyInvite(creatureThing:getId()) end) + end + end + end + end + + if modules.game_ruleviolation.hasWindowAccess() and creatureThing:isPlayer() then + menu:addSeparator() + menu:addOption(tr('Rule Violation'), function() modules.game_ruleviolation.show(creatureThing:getName()) end) + end + + menu:addSeparator() + menu:addOption(tr('Copy Name'), function() g_window.setClipboardText(creatureThing:getName()) end) + end + + -- hooked menu options + for _,category in pairs(hookedMenuOptions) do + if not isMenuHookCategoryEmpty(category) then + menu:addSeparator() + for name,opt in pairs(category) do + if opt and opt.condition(menuPosition, lookThing, useThing, creatureThing) then + menu:addOption(name, function() opt.callback(menuPosition, + lookThing, useThing, creatureThing) end, opt.shortcut) + end + end + end + end + + if g_game.getFeature(GameBot) and useThing and useThing:isItem() then + menu:addSeparator() + if useThing:getSubType() > 1 then + menu:addOption("ID: " .. useThing:getId() .. " SubType: " .. useThing:getSubType(), function() end) + else + menu:addOption("ID: " .. useThing:getId(), function() end) + end + end + + menu:display(menuPosition) +end + +function processMouseAction(menuPosition, mouseButton, autoWalkPos, lookThing, useThing, creatureThing, attackCreature, marking) + local keyboardModifiers = g_keyboard.getModifiers() + + if g_app.isMobile() then + if mouseButton == MouseRightButton then + createThingMenu(menuPosition, lookThing, useThing, creatureThing) + return true + end + if mouseButton ~= MouseLeftButton and mouseButton ~= MouseTouch2 and mouseButton ~= MouseTouch3 then + return false + end + local action = getLeftAction() + if action == "look" then + if lookThing then + resetLeftActions() + g_game.look(lookThing) + return true + end + return true + elseif action == "use" then + if useThing then + resetLeftActions() + if useThing:isContainer() then + if useThing:getParentContainer() then + g_game.open(useThing, useThing:getParentContainer()) + else + g_game.open(useThing) + end + return true + elseif useThing:isMultiUse() then + startUseWith(useThing) + return true + else + g_game.use(useThing) + return true + end + end + return true + elseif action == "attack" then + if attackCreature and attackCreature ~= player then + resetLeftActions() + g_game.attack(attackCreature) + return true + elseif creatureThing and creatureThing ~= player and creatureThing:getPosition().z == autoWalkPos.z then + resetLeftActions() + g_game.attack(creatureThing) + return true + end + return true + elseif action == "follow" then + if attackCreature and attackCreature ~= player then + resetLeftActions() + g_game.follow(attackCreature) + return true + elseif creatureThing and creatureThing ~= player and creatureThing:getPosition().z == autoWalkPos.z then + resetLeftActions() + g_game.follow(creatureThing) + return true + end + return true + elseif not autoWalkPos and useThing then + createThingMenu(menuPosition, lookThing, useThing, creatureThing) + return true + end + elseif not modules.client_options.getOption('classicControl') then + if keyboardModifiers == KeyboardNoModifier and mouseButton == MouseRightButton then + createThingMenu(menuPosition, lookThing, useThing, creatureThing) + return true + elseif lookThing and keyboardModifiers == KeyboardShiftModifier and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then + g_game.look(lookThing) + return true + elseif useThing and keyboardModifiers == KeyboardCtrlModifier and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then + if useThing:isContainer() then + if useThing:getParentContainer() then + g_game.open(useThing, useThing:getParentContainer()) + else + g_game.open(useThing) + end + return true + elseif useThing:isMultiUse() then + startUseWith(useThing) + return true + else + g_game.use(useThing) + return true + end + return true + elseif attackCreature and g_keyboard.isAltPressed() and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then + g_game.attack(attackCreature) + return true + elseif creatureThing and creatureThing:getPosition().z == autoWalkPos.z and g_keyboard.isAltPressed() and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then + g_game.attack(creatureThing) + return true + end + else -- classic control + if useThing and keyboardModifiers == KeyboardNoModifier and mouseButton == MouseRightButton and not g_mouse.isPressed(MouseLeftButton) then + local player = g_game.getLocalPlayer() + if attackCreature and attackCreature ~= player then + g_game.attack(attackCreature) + return true + elseif creatureThing and creatureThing ~= player and creatureThing:getPosition().z == autoWalkPos.z then + g_game.attack(creatureThing) + return true + elseif useThing:isContainer() then + if useThing:getParentContainer() then + g_game.open(useThing, useThing:getParentContainer()) + return true + else + g_game.open(useThing) + return true + end + elseif useThing:isMultiUse() then + startUseWith(useThing) + return true + else + g_game.use(useThing) + return true + end + return true + elseif lookThing and keyboardModifiers == KeyboardShiftModifier and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then + g_game.look(lookThing) + return true + elseif lookThing and ((g_mouse.isPressed(MouseLeftButton) and mouseButton == MouseRightButton) or (g_mouse.isPressed(MouseRightButton) and mouseButton == MouseLeftButton)) then + g_game.look(lookThing) + return true + elseif useThing and keyboardModifiers == KeyboardCtrlModifier and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then + createThingMenu(menuPosition, lookThing, useThing, creatureThing) + return true + elseif attackCreature and g_keyboard.isAltPressed() and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then + g_game.attack(attackCreature) + return true + elseif creatureThing and creatureThing:getPosition().z == autoWalkPos.z and g_keyboard.isAltPressed() and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then + g_game.attack(creatureThing) + return true + end + end + + local player = g_game.getLocalPlayer() + player:stopAutoWalk() + + if autoWalkPos and keyboardModifiers == KeyboardNoModifier and (mouseButton == MouseLeftButton or mouseButton == MouseTouch2 or mouseButton == MouseTouch3) then + local autoWalkTile = g_map.getTile(autoWalkPos) + if autoWalkTile and not autoWalkTile:isWalkable(true) then + modules.game_textmessage.displayFailureMessage(tr('Sorry, not possible.')) + return false + end + player:autoWalk(autoWalkPos) + return true + end + + return false +end + +function moveStackableItem(item, toPos) + if countWindow then + return + end + if g_keyboard.isCtrlPressed() then + g_game.move(item, toPos, item:getCount()) + return + elseif g_keyboard.isShiftPressed() then + g_game.move(item, toPos, 1) + return + end + local count = item:getCount() + + countWindow = g_ui.createWidget('CountWindow', rootWidget) + local itembox = countWindow:getChildById('item') + local scrollbar = countWindow:getChildById('countScrollBar') + itembox:setItemId(item:getId()) + itembox:setItemCount(count) + scrollbar:setMaximum(count) + scrollbar:setMinimum(1) + scrollbar:setValue(count) + + local spinbox = countWindow:getChildById('spinBox') + spinbox:setMaximum(count) + spinbox:setMinimum(0) + spinbox:setValue(0) + spinbox:hideButtons() + spinbox:focus() + spinbox.firstEdit = true + + local spinBoxValueChange = function(self, value) + spinbox.firstEdit = false + scrollbar:setValue(value) + end + spinbox.onValueChange = spinBoxValueChange + + local check = function() + if spinbox.firstEdit then + spinbox:setValue(spinbox:getMaximum()) + spinbox.firstEdit = false + end + end + local okButton = countWindow:getChildById('buttonOk') + local moveFunc = function() + g_game.move(item, toPos, itembox:getItemCount()) + okButton:getParent():destroy() + countWindow = nil + end + local cancelButton = countWindow:getChildById('buttonCancel') + local cancelFunc = function() + cancelButton:getParent():destroy() + countWindow = nil + end + + + g_keyboard.bindKeyPress("Up", function() check() spinbox:up() end, spinbox) + g_keyboard.bindKeyPress("Down", function() check() spinbox:down() end, spinbox) + g_keyboard.bindKeyPress("Right", function() check() spinbox:up() end, spinbox) + g_keyboard.bindKeyPress("Left", function() check() spinbox:down() end, spinbox) + g_keyboard.bindKeyPress("PageUp", function() check() spinbox:setValue(spinbox:getValue()+10) end, spinbox) + g_keyboard.bindKeyPress("PageDown", function() check() spinbox:setValue(spinbox:getValue()-10) end, spinbox) + g_keyboard.bindKeyPress("Enter", function() moveFunc() end, spinbox) + + scrollbar.onValueChange = function(self, value) + itembox:setItemCount(value) + spinbox.onValueChange = nil + spinbox:setValue(value) + spinbox.onValueChange = spinBoxValueChange + end + countWindow.onEnter = moveFunc + countWindow.onEscape = cancelFunc + + okButton.onClick = moveFunc + cancelButton.onClick = cancelFunc +end + +function getRootPanel() + return gameRootPanel +end + +function getMapPanel() + return gameMapPanel +end + +function getRightPanel() + if gameRightPanels:getChildCount() == 0 then + addRightPanel() + end + return gameRightPanels:getChildByIndex(-1) +end + +function getLeftPanel() + if gameLeftPanels:getChildCount() >= 1 then + return gameLeftPanels:getChildByIndex(-1) + end + return getRightPanel() +end + +function getContainerPanel() + local containerPanel = g_settings.getNumber("containerPanel") + if containerPanel >= 5 then + containerPanel = containerPanel - 4 + return gameRightPanels:getChildByIndex(math.min(containerPanel, gameRightPanels:getChildCount())) + end + if gameLeftPanels:getChildCount() == 0 then + return getRightPanel() + end + return gameLeftPanels:getChildByIndex(math.min(containerPanel, gameLeftPanels:getChildCount())) +end + +local function addRightPanel() + if gameRightPanels:getChildCount() >= 4 then + return + end + local panel = g_ui.createWidget('GameSidePanel') + panel:setId("rightPanel" .. (gameRightPanels:getChildCount() + 1)) + gameRightPanels:insertChild(1, panel) +end + +local function addLeftPanel() + if gameLeftPanels:getChildCount() >= 4 then + return + end + local panel = g_ui.createWidget('GameSidePanel') + panel:setId("leftPanel" .. (gameLeftPanels:getChildCount() + 1)) + gameLeftPanels:addChild(panel) +end + +local function removeRightPanel() + if gameRightPanels:getChildCount() <= 1 then + return + end + local panel = gameRightPanels:getChildByIndex(1) + panel:moveTo(gameRightPanels:getChildByIndex(2)) + gameRightPanels:removeChild(panel) +end + +local function removeLeftPanel() + if gameLeftPanels:getChildCount() == 0 then + return + end + local panel = gameLeftPanels:getChildByIndex(-1) + if gameLeftPanels:getChildCount() >= 2 then + panel:moveTo(gameLeftPanels:getChildByIndex(-2)) + else + panel:moveTo(gameRightPanels:getChildByIndex(1)) + end + gameLeftPanels:removeChild(panel) +end + +function getBottomPanel() + return gameBottomPanel +end + +function getBottomActionPanel() + return gameBottomActionPanel +end + +function getLeftActionPanel() + return gameLeftActionPanel +end + +function getRightActionPanel() + return gameRightActionPanel +end + +function getTopBar() + return gameTopBar +end + +function refreshViewMode() + local classic = g_settings.getBoolean("classicView") and not g_app.isMobile() + local rightPanels = g_settings.getNumber("rightPanels") - gameRightPanels:getChildCount() + local leftPanels = g_settings.getNumber("leftPanels") - 1 - gameLeftPanels:getChildCount() + + while rightPanels ~= 0 do + if rightPanels > 0 then + addRightPanel() + rightPanels = rightPanels - 1 + else + removeRightPanel() + rightPanels = rightPanels + 1 + end + end + while leftPanels ~= 0 do + if leftPanels > 0 then + addLeftPanel() + leftPanels = leftPanels - 1 + else + removeLeftPanel() + leftPanels = leftPanels + 1 + end + end + + if not g_game.isOnline() then + return + end + + local minimumWidth = (g_settings.getNumber("rightPanels") + g_settings.getNumber("leftPanels") - 1) * 200 + 200 + minimumWidth = math.max(minimumWidth, g_resources.getLayout() == "mobile" and 640 or 800) + g_window.setMinimumSize({ width = minimumWidth, height = (g_resources.getLayout() == "mobile" and 360 or 600)}) + if g_window.getWidth() < minimumWidth then + local oldPos = g_window.getPosition() + local size = { width = minimumWidth, height = g_window.getHeight() } + g_window.resize(size) + g_window.move(oldPos) + end + + for i=1,gameRightPanels:getChildCount()+gameLeftPanels:getChildCount() do + local panel + if i > gameRightPanels:getChildCount() then + panel = gameLeftPanels:getChildByIndex(i - gameRightPanels:getChildCount()) + else + panel = gameRightPanels:getChildByIndex(i) + end + if classic then + panel:setImageColor('white') + else + panel:setImageColor('alpha') + end + end + + if classic then + gameRightPanels:setMarginTop(0) + gameLeftPanels:setMarginTop(0) + gameMapPanel:setMarginLeft(0) + gameMapPanel:setMarginRight(0) + gameMapPanel:setMarginTop(0) + end + + gameMapPanel:setVisibleDimension({ width = 15, height = 11 }) + + if classic then + g_game.changeMapAwareRange(19, 15) + gameMapPanel:addAnchor(AnchorLeft, 'gameLeftActionPanel', AnchorRight) + gameMapPanel:addAnchor(AnchorRight, 'gameRightActionPanel', AnchorLeft) + gameMapPanel:addAnchor(AnchorBottom, 'gameBottomActionPanel', AnchorTop) + gameMapPanel:addAnchor(AnchorTop, 'gameTopBar', AnchorBottom) + gameMapPanel:setKeepAspectRatio(true) + gameMapPanel:setLimitVisibleRange(false) + gameMapPanel:setZoom(11) + gameMapPanel:setOn(false) -- frame + + modules.client_topmenu.getTopMenu():setImageColor('white') + + if modules.game_console then + modules.game_console.switchMode(false) + end + else + g_game.changeMapAwareRange(31, 21) + gameMapPanel:fill('parent') + gameMapPanel:setKeepAspectRatio(false) + gameMapPanel:setLimitVisibleRange(false) + gameMapPanel:setOn(true) + if g_app.isMobile() then + gameMapPanel:setZoom(11) + else + gameMapPanel:setZoom(15) + end + + modules.client_topmenu.getTopMenu():setImageColor('#ffffff66') + if g_app.isMobile() then + gameMapPanel:setMarginTop(-32) + end + if modules.game_console then + modules.game_console.switchMode(true) + end + end + if modules.game_actionbar then + modules.game_actionbar.switchMode(not classic) + end + + if g_settings.getBoolean("cacheMap") then + g_game.enableFeature(GameBiggerMapCache) + end + + updateSize() +end + +function limitZoom() + limitedZoom = true +end + +function updateSize() + if g_app.isMobile() then return end + + local classic = g_settings.getBoolean("classicView") + local height = gameMapPanel:getHeight() + local width = gameMapPanel:getWidth() + + if not classic then + local rheight = gameRootPanel:getHeight() + local rwidth = gameRootPanel:getWidth() + + local dimenstion = gameMapPanel:getVisibleDimension() + local zoom = gameMapPanel:getZoom() + local awareRange = g_map.getAwareRange() + local dheight = dimenstion.height + local dwidth = dimenstion.width + local tileSize = rheight / dheight + local maxWidth = tileSize * (awareRange.width + 1) + if g_game.getFeature(GameChangeMapAwareRange) and g_game.getFeature(GameNewWalking) then + maxWidth = tileSize * (awareRange.width - 1) + end + gameMapPanel:setMarginTop(-tileSize) + if modules.game_stats then + modules.game_stats.ui:setMarginTop(tileSize) + end + if g_settings.getBoolean("cacheMap") then + gameMapPanel:setMarginLeft(0) + gameMapPanel:setMarginRight(0) + else + local margin = math.max(0, math.floor((rwidth - maxWidth) / 2)) + gameMapPanel:setMarginLeft(margin) + gameMapPanel:setMarginRight(margin) + end + + if modules.game_bot then + for i, child in ipairs(gameMapPanel:getChildren()) do + if child.botIcon and child.onGeometryChange then + child.onGeometryChange(child) + end + end + end + else + if modules.game_stats then + modules.game_stats.ui:setMarginTop(0) + end + end + + --[[ + local maxWidth = math.floor(height * 2) + local extraMargin = 0 + if width >= maxWidth then + extraMargin = math.ceil((width - maxWidth) / 2) + end + local bottomMaxWidth = 1200 -- something broken, it's not pixels + local bottomMargin = 0 + if width > bottomMaxWidth then + bottomMargin = math.ceil((width - bottomMaxWidth) / 2) + end + gameMapPanel:setMarginLeft(extraMargin) + gameMapPanel:setMarginRight(extraMargin) ]] +end + +function setupLeftActions() + if not g_app.isMobile() then return end + for _, widget in ipairs(gameLeftActions:getChildren()) do + widget.image:setChecked(false) + widget.lastClicked = 0 + widget.onClick = function() + if widget.image:isChecked() then + widget.image:setChecked(false) + if widget.doubleClickAction and widget.lastClicked + 200 > g_clock.millis() then + widget.doubleClickAction() + end + return + end + resetLeftActions() + widget.image:setChecked(true) + widget.lastClicked = g_clock.millis() + end + end + if gameLeftActions.use then + gameLeftActions.use.doubleClickAction = function() + local player = g_game.getLocalPlayer() + local dir = player:getDirection() + local usePos = player:getPrewalkingPosition(true) + if dir == North then + usePos.y = usePos.y - 1 + elseif dir == East then + usePos.x = usePos.x + 1 + elseif dir == South then + usePos.y = usePos.y + 1 + elseif dir == West then + usePos.x = usePos.x - 1 + end + local tile = g_map.getTile(usePos) + if not tile then return end + local thing = tile:getTopUseThing() + if thing then + g_game.use(thing) + end + end + end + if gameLeftActions.attack then + gameLeftActions.attack.doubleClickAction = function() + local battlePanel = modules.game_battle.battlePanel + local attackedCreature = g_game.getAttackingCreature() + local child = battlePanel:getFirstChild() + if child and (not child.creature or not child:isOn()) then + child = nil + end + if child then + g_game.attack(child.creature) + else + g_game.attack(nil) + end + end + end + if gameLeftActions.follow then + gameLeftActions.follow.doubleClickAction = function() + local battlePanel = modules.game_battle.battlePanel + local attackedCreature = g_game.getAttackingCreature() + local child = battlePanel:getFirstChild() + if child and (not child.creature or not child:isOn()) then + child = nil + end + if child then + g_game.follow(child.creature) + else + g_game.follow(nil) + end + end + end + if gameLeftActions.look then + gameLeftActions.look.doubleClickAction = function() + local battlePanel = modules.game_battle.battlePanel + local attackedCreature = g_game.getAttackingCreature() + local child = battlePanel:getFirstChild() + if child and (not child.creature or child:isHidden()) then + child = nil + end + if child then + g_game.look(child.creature) + end + end + end + if not gameLeftActions.chat then return end + gameLeftActions.chat.onClick = function() + if gameBottomPanel:getHeight() <= 5 then + gameBottomPanel:setHeight(90) + else + gameBottomPanel:setHeight(0) + end + end +end + +function resetLeftActions() + for _, widget in ipairs(gameLeftActions:getChildren()) do + widget.image:setChecked(false) + widget.lastClicked = 0 + end +end + +function getLeftAction() + for _, widget in ipairs(gameLeftActions:getChildren()) do + if widget.image:isChecked() then + return widget:getId() + end + end + return "" +end + +function isChatVisible() + return gameBottomPanel:getHeight() >= 5 +end \ No newline at end of file diff --git a/800OTClient/modules/game_interface/gameinterface.otui b/800OTClient/modules/game_interface/gameinterface.otui new file mode 100644 index 0000000..664f168 --- /dev/null +++ b/800OTClient/modules/game_interface/gameinterface.otui @@ -0,0 +1,208 @@ +GameSidePanel < UIMiniWindowContainer + image-source: /images/ui/panel_side + image-border: 4 + padding: 3 + padding-top: 0 + width: 198 + focusable: false + on: true + layout: + type: verticalBox + $mobile: + padding: 0 + width: 200 + + +GameMapPanel < UIGameMap + padding: 4 + image-source: /images/ui/panel_map + image-border: 4 + + $on: + padding: 0 + +GameAction < UIButton + size: 64 64 + phantom: false + + UIButton + id: image + size: 48 48 + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + phantom: true + $checked: + opacity: 1.0 + background: #00FF0033 + $!checked: + opacity: 0.6 + background: alpha + + +UIWidget + id: gameRootPanel + anchors.fill: parent + + GameMapPanel + id: gameMapPanel + anchors.left: gameLeftPanels.right + anchors.right: gameRightPanels.left + anchors.top: parent.top + anchors.bottom: gameBottomPanel.top + focusable: false + + Panel + id: gameLeftActions + focusable: false + anchors.bottom: parent.bottom + anchors.left: parent.left + width: 64 + + $!mobile: + visible: false + width: 0 + + layout: + type: verticalBox + fit-children: true + + GameAction + id: use + @onSetup: self.image:setImageSource("/images/game/mobile/use") + GameAction + id: attack + @onSetup: self.image:setImageSource("/images/game/mobile/attack") + GameAction + id: follow + @onSetup: self.image:setImageSource("/images/game/mobile/follow") + GameAction + id: look + @onSetup: self.image:setImageSource("/images/game/mobile/look") + GameAction + id: chat + @onSetup: self.image:setImageSource("/images/game/mobile/chat") + + Panel + id: gameLeftPanels + focusable: false + anchors.top: parent.top + anchors.bottom: parent.bottom + $!mobile: + anchors.left: parent.left + $mobile: + anchors.left: gameLeftActions.right + + layout: + type: horizontalBox + fit-children: true + spacing: -1 + + Panel + id: gameLeftActionPanel + phantom: true + focusable: false + anchors.top: gameTopBar.bottom + anchors.left: gameLeftPanels.right + anchors.bottom: bottomSplitter.top + margin-top: 3 + + $mobile: + visible: false + + layout: + type: horizontalBox + fit-children: true + + Panel + id: gameRightPanels + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + focusable: false + layout: + type: horizontalBox + fit-children: true + spacing: -1 + + Panel + id: gameRightActionPanel + phantom: true + focusable: false + anchors.top: gameTopBar.bottom + anchors.right: gameRightPanels.left + anchors.bottom: bottomSplitter.top + margin-top: 3 + + $mobile: + visible: false + + layout: + type: horizontalBox + fit-children: true + + Splitter + id: bottomSplitter + anchors.left: gameLeftPanels.right + anchors.right: gameRightPanels.left + anchors.bottom: parent.bottom + height: 5 + relative-margin: bottom + margin-bottom: 150 + @canUpdateMargin: function(self, newMargin) if modules.client_options.getOption('dontStretchShrink') then return self:getMarginBottom() end return math.max(math.min(newMargin, self:getParent():getHeight() - 150), 80) end + @onGeometryChange: function(self) self:setMarginBottom(math.min(math.max(self:getParent():getHeight() - 150, 80), self:getMarginBottom())) end + + $mobile: + visible: false + + Panel + id: gameBottomActionPanel + phantom: true + focusable: false + + $!mobile: + anchors.left: bottomSplitter.left + anchors.right: bottomSplitter.right + anchors.top: bottomSplitter.top + margin-top: 3 + + $mobile: + anchors.left: gameLeftPanels.right + anchors.right: gameRightPanels.left + anchors.bottom: gameBottomPanel.top + + layout: + type: verticalBox + fit-children: true + + Panel + id: gameBottomPanel + $!mobile: + anchors.left: gameBottomActionPanel.left + anchors.right: gameBottomActionPanel.right + anchors.top: gameBottomActionPanel.bottom + anchors.bottom: parent.bottom + + $mobile: + anchors.left: gameLeftPanels.right + anchors.right: gameRightPanels.left + anchors.bottom: parent.bottom + + UIWidget + id: mouseGrabber + focusable: false + visible: false + + Panel + id: gameTopBar + image-source: /images/ui/panel_bottom2 + anchors.top: parent.top + anchors.left: gameBottomActionPanel.left + anchors.right: gameBottomActionPanel.right + focusable: false + + $mobile: + height: 0 + + layout: + type: verticalBox + fit-children: true \ No newline at end of file diff --git a/800OTClient/modules/game_interface/interface.otmod b/800OTClient/modules/game_interface/interface.otmod new file mode 100644 index 0000000..4572b90 --- /dev/null +++ b/800OTClient/modules/game_interface/interface.otmod @@ -0,0 +1,46 @@ +Module + name: game_interface + description: Create the game interface, where the ingame stuff starts + author: OTClient team + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ widgets/uigamemap, gameinterface ] + load-later: + - game_buttons + - game_hotkeys + - game_questlog + - game_textmessage + - game_console + - game_outfit + - game_healthinfo + - game_skills + - game_inventory + - game_containers + - game_viplist + - game_battle + - game_minimap + - game_npctrade + - game_textwindow + - game_playertrade + - game_bugreport + - game_playerdeath + - game_playermount + - game_ruleviolation + - game_market + - game_spelllist + - game_cooldown + - game_modaldialog + - game_unjustifiedpoints + - game_walking + - game_shop + - game_itemselector + - client_textedit + - client_profiles + - game_actionbar + - game_prey + - game_imbuing + - game_stats + - game_shaders + - game_bot + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_interface/styles/countwindow.otui b/800OTClient/modules/game_interface/styles/countwindow.otui new file mode 100644 index 0000000..6cef134 --- /dev/null +++ b/800OTClient/modules/game_interface/styles/countwindow.otui @@ -0,0 +1,53 @@ +CountWindow < MainWindow + id: countWindow + !text: tr('Move Stackable Item') + size: 196 90 + + SpinBox + id: spinBox + anchors.left: parent.left + anchors.top: parent.top + width: 1 + height: 1 + phantom: true + margin-top: 2 + focusable: true + + Item + id: item + anchors.left: parent.left + anchors.top: parent.top + margin-top: 2 + margin-left: -4 + focusable: false + virtual: true + + HorizontalScrollBar + id: countScrollBar + anchors.left: prev.right + anchors.right: parent.right + anchors.top: prev.top + margin-left: 10 + margin-top: -2 + focusable: false + + Button + id: buttonCancel + !text: tr('Cancel') + height: 20 + anchors.left: countScrollBar.horizontalCenter + anchors.right: countScrollBar.right + anchors.top: countScrollBar.bottom + margin-top: 7 + focusable: false + + Button + id: buttonOk + !text: tr('Ok') + height: 20 + anchors.right: countScrollBar.horizontalCenter + anchors.left: countScrollBar.left + anchors.top: countScrollBar.bottom + margin-top: 7 + margin-right: 6 + focusable: false diff --git a/800OTClient/modules/game_interface/widgets/uigamemap.lua b/800OTClient/modules/game_interface/widgets/uigamemap.lua new file mode 100644 index 0000000..67d9bf9 --- /dev/null +++ b/800OTClient/modules/game_interface/widgets/uigamemap.lua @@ -0,0 +1,196 @@ +UIGameMap = extends(UIMap, "UIGameMap") + +function UIGameMap.create() + local gameMap = UIGameMap.internalCreate() + gameMap:setKeepAspectRatio(true) + gameMap:setVisibleDimension({width = 15, height = 11}) + gameMap:setDrawLights(true) + gameMap.markedThing = nil + gameMap.blockNextRelease = 0 + gameMap:updateMarkedCreature() + return gameMap +end + +function UIGameMap:onDestroy() + if self.updateMarkedCreatureEvent then + removeEvent(self.updateMarkedCreatureEvent) + end +end + +function UIGameMap:markThing(thing, color) + if self.markedThing == thing then + return + end + if self.markedThing then + self.markedThing:setMarked('') + end + + self.markedThing = thing + if self.markedThing and g_settings.getBoolean('highlightThingsUnderCursor') then + self.markedThing:setMarked(color) + end +end + +function UIGameMap:onDragEnter(mousePos) + local tile = self:getTile(mousePos) + if not tile then return false end + + local thing = tile:getTopMoveThing() + if not thing then return false end + + self.currentDragThing = thing + + g_mouse.pushCursor('target') + self.allowNextRelease = false + return true +end + +function UIGameMap:onDragLeave(droppedWidget, mousePos) + self.currentDragThing = nil + self.hoveredWho = nil + g_mouse.popCursor('target') + return true +end + +function UIGameMap:onDrop(widget, mousePos) + if not self:canAcceptDrop(widget, mousePos) then return false end + + local tile = self:getTile(mousePos) + if not tile then return false end + + local thing = widget.currentDragThing + local toPos = tile:getPosition() + + local thingPos = thing:getPosition() + if thingPos.x == toPos.x and thingPos.y == toPos.y and thingPos.z == toPos.z then return false end + + if thing:isItem() and thing:getCount() > 1 then + modules.game_interface.moveStackableItem(thing, toPos) + else + g_game.move(thing, toPos, 1) + end + + return true +end + +function UIGameMap:onMouseMove(mousePos, mouseMoved) + self.mousePos = mousePos + return false +end + +function UIGameMap:onDragMove(mousePos, mouseMoved) + self.mousePos = mousePos + return false +end + +function UIGameMap:updateMarkedCreature() + self.updateMarkedCreatureEvent = scheduleEvent(function() self:updateMarkedCreature() end, 100) + if self.mousePos and g_game.isOnline() then + self.markingMouseRelease = true + self:onMouseRelease(self.mousePos, MouseRightButton) + self.markingMouseRelease = false + end +end + +function UIGameMap:onMousePress() + if not self:isDragging() and self.blockNextRelease < g_clock.millis() then + self.allowNextRelease = true + self.markingMouseRelease = false + end +end + +function UIGameMap:blockNextMouseRelease(postAction) + self.allowNextRelease = false + if postAction then + self.blockNextRelease = g_clock.millis() + 150 + else + self.blockNextRelease = g_clock.millis() + 250 + end +end + +function UIGameMap:onMouseRelease(mousePosition, mouseButton) + if not self.allowNextRelease and not self.markingMouseRelease then + return true + end + local autoWalkPos = self:getPosition(mousePosition) + local positionOffset = self:getPositionOffset(mousePosition) + + -- happens when clicking outside of map boundaries + if not autoWalkPos then + if self.markingMouseRelease then + self:markThing(nil) + end + return false + end + + local localPlayerPos = g_game.getLocalPlayer():getPosition() + if autoWalkPos.z ~= localPlayerPos.z then + local dz = autoWalkPos.z - localPlayerPos.z + autoWalkPos.x = autoWalkPos.x + dz + autoWalkPos.y = autoWalkPos.y + dz + autoWalkPos.z = localPlayerPos.z + end + + local lookThing + local useThing + local creatureThing + local multiUseThing + local attackCreature + + local tile = self:getTile(mousePosition) + if tile then + lookThing = tile:getTopLookThingEx(positionOffset) + useThing = tile:getTopUseThing() + creatureThing = tile:getTopCreatureEx(positionOffset) + end + + local autoWalkTile = g_map.getTile(autoWalkPos) + if autoWalkTile then + attackCreature = autoWalkTile:getTopCreatureEx(positionOffset) + end + + if self.markingMouseRelease then + if attackCreature then + self:markThing(attackCreature, 'yellow') + elseif creatureThing then + self:markThing(creatureThing, 'yellow') + elseif useThing and not useThing:isGround() then + self:markThing(useThing, 'yellow') + elseif lookThing and not lookThing:isGround() then + self:markThing(lookThing, 'yellow') + else + self:markThing(nil, '') + end + return + end + + local ret = modules.game_interface.processMouseAction(mousePosition, mouseButton, autoWalkPos, lookThing, useThing, creatureThing, attackCreature, self.markingMouseRelease) + if ret then + self.allowNextRelease = false + end + + return ret +end + +function UIGameMap:onTouchRelease(mousePosition, mouseButton) + if mouseButton ~= MouseTouch then + return self:onMouseRelease(mousePosition, mouseButton) + end +end + +function UIGameMap:canAcceptDrop(widget, mousePos) + if not widget or not widget.currentDragThing then return false end + + local children = rootWidget:recursiveGetChildrenByPos(mousePos) + for i=1,#children do + local child = children[i] + if child == self then + return true + elseif not child:isPhantom() then + return false + end + end + + error('Widget ' .. self:getId() .. ' not in drop list.') + return false +end diff --git a/800OTClient/modules/game_inventory/inventory.lua b/800OTClient/modules/game_inventory/inventory.lua new file mode 100644 index 0000000..b50a680 --- /dev/null +++ b/800OTClient/modules/game_inventory/inventory.lua @@ -0,0 +1,487 @@ +Icons = {} +Icons[PlayerStates.Poison] = { tooltip = tr('You are poisoned'), path = '/images/game/states/poisoned', id = 'condition_poisoned' } +Icons[PlayerStates.Burn] = { tooltip = tr('You are burning'), path = '/images/game/states/burning', id = 'condition_burning' } +Icons[PlayerStates.Energy] = { tooltip = tr('You are electrified'), path = '/images/game/states/electrified', id = 'condition_electrified' } +Icons[PlayerStates.Drunk] = { tooltip = tr('You are drunk'), path = '/images/game/states/drunk', id = 'condition_drunk' } +Icons[PlayerStates.ManaShield] = { tooltip = tr('You are protected by a magic shield'), path = '/images/game/states/magic_shield', id = 'condition_magic_shield' } +Icons[PlayerStates.Paralyze] = { tooltip = tr('You are paralysed'), path = '/images/game/states/slowed', id = 'condition_slowed' } +Icons[PlayerStates.Haste] = { tooltip = tr('You are hasted'), path = '/images/game/states/haste', id = 'condition_haste' } +Icons[PlayerStates.Swords] = { tooltip = tr('You may not logout during a fight'), path = '/images/game/states/logout_block', id = 'condition_logout_block' } +Icons[PlayerStates.Drowning] = { tooltip = tr('You are drowning'), path = '/images/game/states/drowning', id = 'condition_drowning' } +Icons[PlayerStates.Freezing] = { tooltip = tr('You are freezing'), path = '/images/game/states/freezing', id = 'condition_freezing' } +Icons[PlayerStates.Dazzled] = { tooltip = tr('You are dazzled'), path = '/images/game/states/dazzled', id = 'condition_dazzled' } +Icons[PlayerStates.Cursed] = { tooltip = tr('You are cursed'), path = '/images/game/states/cursed', id = 'condition_cursed' } +Icons[PlayerStates.PartyBuff] = { tooltip = tr('You are strengthened'), path = '/images/game/states/strengthened', id = 'condition_strengthened' } +Icons[PlayerStates.PzBlock] = { tooltip = tr('You may not logout or enter a protection zone'), path = '/images/game/states/protection_zone_block', id = 'condition_protection_zone_block' } +Icons[PlayerStates.Pz] = { tooltip = tr('You are within a protection zone'), path = '/images/game/states/protection_zone', id = 'condition_protection_zone' } +Icons[PlayerStates.Bleeding] = { tooltip = tr('You are bleeding'), path = '/images/game/states/bleeding', id = 'condition_bleeding' } +Icons[PlayerStates.Hungry] = { tooltip = tr('You are hungry'), path = '/images/game/states/hungry', id = 'condition_hungry' } + +InventorySlotStyles = { + [InventorySlotHead] = "HeadSlot", + [InventorySlotNeck] = "NeckSlot", + [InventorySlotBack] = "BackSlot", + [InventorySlotBody] = "BodySlot", + [InventorySlotRight] = "RightSlot", + [InventorySlotLeft] = "LeftSlot", + [InventorySlotLeg] = "LegSlot", + [InventorySlotFeet] = "FeetSlot", + [InventorySlotFinger] = "FingerSlot", + [InventorySlotAmmo] = "AmmoSlot" +} + +inventoryWindow = nil +inventoryPanel = nil +inventoryButton = nil +purseButton = nil + +combatControlsWindow = nil +fightOffensiveBox = nil +fightBalancedBox = nil +fightDefensiveBox = nil +chaseModeButton = nil +safeFightButton = nil +mountButton = nil +fightModeRadioGroup = nil +buttonPvp = nil + +soulLabel = nil +capLabel = nil +conditionPanel = nil + +function init() + connect(LocalPlayer, { + onInventoryChange = onInventoryChange, + onBlessingsChange = onBlessingsChange + }) + connect(g_game, { onGameStart = refresh }) + + g_keyboard.bindKeyDown('Ctrl+I', toggle) + + + inventoryWindow = g_ui.loadUI('inventory', modules.game_interface.getRightPanel()) + inventoryWindow:disableResize() + inventoryPanel = inventoryWindow:getChildById('contentsPanel'):getChildById('inventoryPanel') + if not inventoryWindow.forceOpen then + inventoryButton = modules.client_topmenu.addRightGameToggleButton('inventoryButton', tr('Inventory') .. ' (Ctrl+I)', '/images/topbuttons/inventory', toggle) + inventoryButton:setOn(true) + end + + purseButton = inventoryWindow:recursiveGetChildById('purseButton') + purseButton.onClick = function() + local purse = g_game.getLocalPlayer():getInventoryItem(InventorySlotPurse) + if purse then + g_game.use(purse) + end + end + + -- controls + fightOffensiveBox = inventoryWindow:recursiveGetChildById('fightOffensiveBox') + fightBalancedBox = inventoryWindow:recursiveGetChildById('fightBalancedBox') + fightDefensiveBox = inventoryWindow:recursiveGetChildById('fightDefensiveBox') + + chaseModeButton = inventoryWindow:recursiveGetChildById('chaseModeBox') + safeFightButton = inventoryWindow:recursiveGetChildById('safeFightBox') + buttonPvp = inventoryWindow:recursiveGetChildById('buttonPvp') + + mountButton = inventoryWindow:recursiveGetChildById('mountButton') + mountButton.onClick = onMountButtonClick + + whiteDoveBox = inventoryWindow:recursiveGetChildById('whiteDoveBox') + whiteHandBox = inventoryWindow:recursiveGetChildById('whiteHandBox') + yellowHandBox = inventoryWindow:recursiveGetChildById('yellowHandBox') + redFistBox = inventoryWindow:recursiveGetChildById('redFistBox') + + fightModeRadioGroup = UIRadioGroup.create() + fightModeRadioGroup:addWidget(fightOffensiveBox) + fightModeRadioGroup:addWidget(fightBalancedBox) + fightModeRadioGroup:addWidget(fightDefensiveBox) + + connect(fightModeRadioGroup, { onSelectionChange = onSetFightMode }) + connect(chaseModeButton, { onCheckChange = onSetChaseMode }) + connect(safeFightButton, { onCheckChange = onSetSafeFight }) + if buttonPvp then + connect(buttonPvp, { onClick = onSetSafeFight2 }) + end + connect(g_game, { + onGameStart = online, + onGameEnd = offline, + onFightModeChange = update, + onChaseModeChange = update, + onSafeFightChange = update, + onPVPModeChange = update, + onWalk = check, + onAutoWalk = check + }) + + connect(LocalPlayer, { onOutfitChange = onOutfitChange }) + + if g_game.isOnline() then + online() + end +-- controls end + +-- status + soulLabel = inventoryWindow:recursiveGetChildById('soulLabel') + capLabel = inventoryWindow:recursiveGetChildById('capLabel') + conditionPanel = inventoryWindow:recursiveGetChildById('conditionPanel') + + + connect(LocalPlayer, { onStatesChange = onStatesChange, + onSoulChange = onSoulChange, + onFreeCapacityChange = onFreeCapacityChange }) +-- status end + + refresh() + inventoryWindow:setup() +end + +function terminate() + disconnect(LocalPlayer, { + onInventoryChange = onInventoryChange, + onBlessingsChange = onBlessingsChange + }) + disconnect(g_game, { onGameStart = refresh }) + + g_keyboard.unbindKeyDown('Ctrl+I') + + -- controls + if g_game.isOnline() then + offline() + end + + fightModeRadioGroup:destroy() + + disconnect(g_game, { + onGameStart = online, + onGameEnd = offline, + onFightModeChange = update, + onChaseModeChange = update, + onSafeFightChange = update, + onPVPModeChange = update, + onWalk = check, + onAutoWalk = check + }) + + disconnect(LocalPlayer, { onOutfitChange = onOutfitChange }) + -- controls end + -- status + disconnect(LocalPlayer, { onStatesChange = onStatesChange, + onSoulChange = onSoulChange, + onFreeCapacityChange = onFreeCapacityChange }) + -- status end + + inventoryWindow:destroy() + if inventoryButton then + inventoryButton:destroy() + end +end + +function toggleAdventurerStyle(hasBlessing) + for slot = InventorySlotFirst, InventorySlotLast do + local itemWidget = inventoryPanel:getChildById('slot' .. slot) + if itemWidget then + itemWidget:setOn(hasBlessing) + end + end +end + +function refresh() + local player = g_game.getLocalPlayer() + for i = InventorySlotFirst, InventorySlotPurse do + if g_game.isOnline() then + onInventoryChange(player, i, player:getInventoryItem(i)) + else + onInventoryChange(player, i, nil) + end + toggleAdventurerStyle(player and Bit.hasBit(player:getBlessings(), Blessings.Adventurer) or false) + end + if player then + onSoulChange(player, player:getSoul()) + onFreeCapacityChange(player, player:getFreeCapacity()) + onStatesChange(player, player:getStates(), 0) + end + + purseButton:setVisible(g_game.getFeature(GamePurseSlot)) +end + +function toggle() + if not inventoryButton then + return + end + if inventoryButton:isOn() then + inventoryWindow:close() + inventoryButton:setOn(false) + else + inventoryWindow:open() + inventoryButton:setOn(true) + end +end + +function onMiniWindowClose() + if not inventoryButton then + return + end + inventoryButton:setOn(false) +end + +-- hooked events +function onInventoryChange(player, slot, item, oldItem) + if slot > InventorySlotPurse then return end + + if slot == InventorySlotPurse then + if g_game.getFeature(GamePurseSlot) then + --purseButton:setEnabled(item and true or false) + end + return + end + + local itemWidget = inventoryPanel:getChildById('slot' .. slot) + if item then + itemWidget:setStyle('InventoryItem') + itemWidget:setItem(item) + else + itemWidget:setStyle(InventorySlotStyles[slot]) + itemWidget:setItem(nil) + end +end + +function onBlessingsChange(player, blessings, oldBlessings) + local hasAdventurerBlessing = Bit.hasBit(blessings, Blessings.Adventurer) + if hasAdventurerBlessing ~= Bit.hasBit(oldBlessings, Blessings.Adventurer) then + toggleAdventurerStyle(hasAdventurerBlessing) + end +end + + +-- controls +function update() + local fightMode = g_game.getFightMode() + if fightMode == FightOffensive then + fightModeRadioGroup:selectWidget(fightOffensiveBox) + elseif fightMode == FightBalanced then + fightModeRadioGroup:selectWidget(fightBalancedBox) + else + fightModeRadioGroup:selectWidget(fightDefensiveBox) + end + + local chaseMode = g_game.getChaseMode() + chaseModeButton:setChecked(chaseMode == ChaseOpponent) + + local safeFight = g_game.isSafeFight() + safeFightButton:setChecked(not safeFight) + if buttonPvp then + if safeFight then + buttonPvp:setOn(false) + else + buttonPvp:setOn(true) + end + end + + if g_game.getFeature(GamePVPMode) then + local pvpMode = g_game.getPVPMode() + local pvpWidget = getPVPBoxByMode(pvpMode) + end +end + +function check() + if modules.client_options.getOption('autoChaseOverride') then + if g_game.isAttacking() and g_game.getChaseMode() == ChaseOpponent then + g_game.setChaseMode(DontChase) + end + end +end + +function online() + local player = g_game.getLocalPlayer() + if player then + local char = g_game.getCharacterName() + + local lastCombatControls = g_settings.getNode('LastCombatControls') + + if not table.empty(lastCombatControls) then + if lastCombatControls[char] then + g_game.setFightMode(lastCombatControls[char].fightMode) + g_game.setChaseMode(lastCombatControls[char].chaseMode) + g_game.setSafeFight(lastCombatControls[char].safeFight) + if lastCombatControls[char].pvpMode then + g_game.setPVPMode(lastCombatControls[char].pvpMode) + end + end + end + + if g_game.getFeature(GamePlayerMounts) then + mountButton:setVisible(true) + mountButton:setChecked(player:isMounted()) + else + mountButton:setVisible(false) + end + end + + update() +end + +function offline() + local lastCombatControls = g_settings.getNode('LastCombatControls') + if not lastCombatControls then + lastCombatControls = {} + end + + conditionPanel:destroyChildren() + + local player = g_game.getLocalPlayer() + if player then + local char = g_game.getCharacterName() + lastCombatControls[char] = { + fightMode = g_game.getFightMode(), + chaseMode = g_game.getChaseMode(), + safeFight = g_game.isSafeFight() + } + + if g_game.getFeature(GamePVPMode) then + lastCombatControls[char].pvpMode = g_game.getPVPMode() + end + + -- save last combat control settings + g_settings.setNode('LastCombatControls', lastCombatControls) + end +end + +function onSetFightMode(self, selectedFightButton) + if selectedFightButton == nil then return end + local buttonId = selectedFightButton:getId() + local fightMode + if buttonId == 'fightOffensiveBox' then + fightMode = FightOffensive + elseif buttonId == 'fightBalancedBox' then + fightMode = FightBalanced + else + fightMode = FightDefensive + end + g_game.setFightMode(fightMode) +end + +function onSetChaseMode(self, checked) + local chaseMode + if checked then + chaseMode = ChaseOpponent + else + chaseMode = DontChase + end + g_game.setChaseMode(chaseMode) +end + +function onSetSafeFight(self, checked) + g_game.setSafeFight(not checked) + if buttonPvp then + if not checked then + buttonPvp:setOn(false) + else + buttonPvp:setOn(true) + end + end +end + +function onSetSafeFight2(self) + onSetSafeFight(self, not safeFightButton:isChecked()) +end + +function onSetPVPMode(self, selectedPVPButton) + if selectedPVPButton == nil then + return + end + + local buttonId = selectedPVPButton:getId() + local pvpMode = PVPWhiteDove + if buttonId == 'whiteDoveBox' then + pvpMode = PVPWhiteDove + elseif buttonId == 'whiteHandBox' then + pvpMode = PVPWhiteHand + elseif buttonId == 'yellowHandBox' then + pvpMode = PVPYellowHand + elseif buttonId == 'redFistBox' then + pvpMode = PVPRedFist + end + + g_game.setPVPMode(pvpMode) +end + +function onMountButtonClick(self, mousePos) + local player = g_game.getLocalPlayer() + if player then + player:toggleMount() + end +end + +function onOutfitChange(localPlayer, outfit, oldOutfit) + if outfit.mount == oldOutfit.mount then + return + end + + mountButton:setChecked(outfit.mount ~= nil and outfit.mount > 0) +end + +function getPVPBoxByMode(mode) + local widget = nil + if mode == PVPWhiteDove then + widget = whiteDoveBox + elseif mode == PVPWhiteHand then + widget = whiteHandBox + elseif mode == PVPYellowHand then + widget = yellowHandBox + elseif mode == PVPRedFist then + widget = redFistBox + end + return widget +end + +-- status +function toggleIcon(bitChanged) + local icon = conditionPanel:getChildById(Icons[bitChanged].id) + if icon then + icon:destroy() + else + icon = loadIcon(bitChanged) + icon:setParent(conditionPanel) + end +end + +function loadIcon(bitChanged) + local icon = g_ui.createWidget('ConditionWidget', conditionPanel) + icon:setId(Icons[bitChanged].id) + icon:setImageSource(Icons[bitChanged].path) + icon:setTooltip(Icons[bitChanged].tooltip) + return icon +end + +function onSoulChange(localPlayer, soul) + if not soul then return end + soulLabel:setText(tr('Soul') .. ':\n' .. soul) +end + +function onFreeCapacityChange(player, freeCapacity) + if not freeCapacity then return end + if freeCapacity > 99 then + freeCapacity = math.floor(freeCapacity * 10) / 10 + end + if freeCapacity > 999 then + freeCapacity = math.floor(freeCapacity) + end + if freeCapacity > 99999 then + freeCapacity = math.min(9999, math.floor(freeCapacity/1000)) .. "k" + end + capLabel:setText(tr('Cap') .. ':\n' .. freeCapacity) +end + +function onStatesChange(localPlayer, now, old) + if now == old then return end + local bitsChanged = bit32.bxor(now, old) + for i = 1, 32 do + local pow = math.pow(2, i-1) + if pow > bitsChanged then break end + local bitChanged = bit32.band(bitsChanged, pow) + if bitChanged ~= 0 then + toggleIcon(bitChanged) + end + end +end \ No newline at end of file diff --git a/800OTClient/modules/game_inventory/inventory.otmod b/800OTClient/modules/game_inventory/inventory.otmod new file mode 100644 index 0000000..5ee5be7 --- /dev/null +++ b/800OTClient/modules/game_inventory/inventory.otmod @@ -0,0 +1,9 @@ +Module + name: game_inventory + description: View local player equipments window + author: baxnie, edubart, BeniS, otclientv8 + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ inventory ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_inventory/inventory.otui b/800OTClient/modules/game_inventory/inventory.otui new file mode 100644 index 0000000..dd01c02 --- /dev/null +++ b/800OTClient/modules/game_inventory/inventory.otui @@ -0,0 +1,6 @@ +InventoryWindow + id: inventoryWindow + !text: tr('Inventory') + @onClose: modules.game_inventory.onMiniWindowClose() + &save: true + &autoOpen: 3 diff --git a/800OTClient/modules/game_itemselector/itemselector.lua b/800OTClient/modules/game_itemselector/itemselector.lua new file mode 100644 index 0000000..33f9437 --- /dev/null +++ b/800OTClient/modules/game_itemselector/itemselector.lua @@ -0,0 +1,74 @@ +local activeWindow + +function init() + g_ui.importStyle('itemselector') + + connect(g_game, { onGameEnd = destroyWindow }) +end + +function terminate() + disconnect(g_game, { onGameEnd = destroyWindow }) + + destroyWindow() +end + +function destroyWindow() + if activeWindow then + activeWindow:destroy() + activeWindow = nil + end +end + +function show(itemWidget) + if not itemWidget then + return + end + if activeWindow then + destroyWindow() + end + local window = g_ui.createWidget('ItemSelectorWindow', rootWidget) + + local destroy = function() + window:destroy() + if window == activeWindow then + activeWindow = nil + end + end + local doneFunc = function() + itemWidget:setItem(Item.create(window.item:getItemId(), window.item:getItemCount())) + destroy() + end + local clearFunc = function() + window.item:setItemId(0) + window.item:setItemCount(0) + doneFunc() + end + + window.clearButton.onClick = clearFunc + window.okButton.onClick = doneFunc + window.cancelButton.onClick = destroy + window.onEnter = doneFunc + window.onEscape = destroy + + window.item:setItem(Item.create(itemWidget:getItemId(), itemWidget:getItemCount())) + + window.itemId:setValue(itemWidget:getItemId()) + if itemWidget:getItemCount() > 1 then + window.itemCount:setValue(itemWidget:getItemCount()) + end + + window.itemId.onValueChange = function(widget, value) + window.item:setItemId(value) + end + window.itemCount.onValueChange = function(widget, value) + window.item:setItemCount(value) + end + + activeWindow = window + activeWindow:raise() + activeWindow:focus() +end + +function hide() + destroyWindow() +end diff --git a/800OTClient/modules/game_itemselector/itemselector.otmod b/800OTClient/modules/game_itemselector/itemselector.otmod new file mode 100644 index 0000000..d8f92b7 --- /dev/null +++ b/800OTClient/modules/game_itemselector/itemselector.otmod @@ -0,0 +1,10 @@ +Module + name: game_itemselector + description: Allow to select item + author: OTClientV8 + website: https://github.com/OTCv8/otclientv8 + sandboxed: true + dependencies: [ game_interface ] + scripts: [ itemselector ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_itemselector/itemselector.otui b/800OTClient/modules/game_itemselector/itemselector.otui new file mode 100644 index 0000000..0e114c6 --- /dev/null +++ b/800OTClient/modules/game_itemselector/itemselector.otui @@ -0,0 +1,72 @@ +ItemSelectorWindow < MainWindow + id: itemSelector + size: 260 120 + !text: tr("Select item") + + Item + id: item + virtual: true + size: 32 32 + margin-top: 10 + anchors.top: parent.top + anchors.left: parent.left + + SpinBox + id: itemId + anchors.top: parent.top + anchors.left: prev.right + margin-top: 15 + margin-left: 5 + padding-left: 5 + width: 70 + minimum: 0 + maximum: 999999 + focusable: true + + Label + anchors.top: parent.top + anchors.left: prev.left + anchors.right: prev.right + text-align: center + !text: tr("Item ID") + + SpinBox + id: itemCount + anchors.top: parent.top + anchors.left: prev.right + margin-top: 15 + margin-left: 5 + padding-left: 5 + width: 120 + minimum: 1 + maximum: 100 + focusable: true + + Label + anchors.top: parent.top + anchors.left: prev.left + anchors.right: prev.right + text-align: center + !text: tr("Count / SubType") + + Button + id: clearButton + !text: tr('Clear') + anchors.bottom: parent.bottom + anchors.left: parent.left + width: 60 + + Button + id: okButton + !text: tr('Ok') + anchors.bottom: parent.bottom + anchors.right: next.left + margin-right: 10 + width: 60 + + Button + id: cancelButton + !text: tr('Cancel') + anchors.bottom: parent.bottom + anchors.right: parent.right + width: 60 diff --git a/800OTClient/modules/game_market/market.lua b/800OTClient/modules/game_market/market.lua new file mode 100644 index 0000000..5c6abb7 --- /dev/null +++ b/800OTClient/modules/game_market/market.lua @@ -0,0 +1,1433 @@ +--[[ + Finalizing Market: + Note: Feel free to work on any area and submit + it as a pull request from your git fork. + + BeniS's Skype: benjiz69 + + List: + * Add offer management: + - Current Offers + - Offer History + + * Clean up the interface building + - Add a new market interface file to handle building? + + * Extend information features + - Hover over offers for purchase information (balance after transaction, etc) + ]] + +Market = {} + +local protocol = runinsandbox('marketprotocol') + +marketWindow = nil +mainTabBar = nil +displaysTabBar = nil +offersTabBar = nil +selectionTabBar = nil + +marketOffersPanel = nil +browsePanel = nil +overviewPanel = nil +itemOffersPanel = nil +itemDetailsPanel = nil +itemStatsPanel = nil +myOffersPanel = nil +currentOffersPanel = nil +myCurrentOffersTab = nil +myOfferHistoryTab = nil +offerHistoryPanel = nil +itemsPanel = nil +selectedOffer = {} +selectedMyOffer = {} + +nameLabel = nil +feeLabel = nil +balanceLabel = nil +totalPriceEdit = nil +piecePriceEdit = nil +amountEdit = nil +searchEdit = nil +radioItemSet = nil +selectedItem = nil +offerTypeList = nil +categoryList = nil +subCategoryList = nil +slotFilterList = nil +createOfferButton = nil +buyButton = nil +sellButton = nil +anonymous = nil +filterButtons = {} + +buyOfferTable = nil +sellOfferTable = nil +detailsTable = nil +buyStatsTable = nil +sellStatsTable = nil + +buyCancelButton = nil +sellCancelButton = nil +buyMyOfferTable = nil +sellMyOfferTable = nil +myOfferHistoryTabel = nil + +offerExhaust = {} +marketOffers = {} +marketItems = {} +marketItemNames = {} +information = {} +currentItems = {} +lastCreatedOffer = 0 +fee = 0 +averagePrice = 0 +tibiaCoins = 0 + +loaded = false + +local function isItemValid(item, category, searchFilter) + if not item or not item.marketData then + return false + end + + if not category then + category = MarketCategory.All + end + if item.marketData.category ~= category and category ~= MarketCategory.All then + return false + end + + -- filter item + local slotFilter = false + if slotFilterList:isEnabled() then + slotFilter = getMarketSlotFilterId(slotFilterList:getCurrentOption().text) + end + local marketData = item.marketData + + local filterVocation = filterButtons[MarketFilters.Vocation]:isChecked() + local filterLevel = filterButtons[MarketFilters.Level]:isChecked() + local filterDepot = filterButtons[MarketFilters.Depot]:isChecked() + + if slotFilter then + if slotFilter ~= 255 and item.thingType:getClothSlot() ~= slotFilter then + return false + end + end + local player = g_game.getLocalPlayer() + if filterLevel and marketData.requiredLevel and player:getLevel() < marketData.requiredLevel then + return false + end + if filterVocation and marketData.restrictVocation and marketData.restrictVocation > 0 then + local voc = Bit.bit(information.vocation) + if not Bit.hasBit(marketData.restrictVocation, voc) then + return false + end + end + if filterDepot and Market.getDepotCount(item.marketData.tradeAs) <= 0 then + return false + end + if searchFilter then + return marketData.name:lower():find(searchFilter) + end + return true +end + +local function clearItems() + currentItems = {} + Market.refreshItemsWidget() +end + +local function clearOffers() + marketOffers[MarketAction.Buy] = {} + marketOffers[MarketAction.Sell] = {} + buyOfferTable:clearData() + sellOfferTable:clearData() +end + +local function clearMyOffers() + marketOffers[MarketAction.Buy] = {} + marketOffers[MarketAction.Sell] = {} + buyMyOfferTable:clearData() + sellMyOfferTable:clearData() + myOfferHistoryTabel:clearData() +end + +local function clearFilters() + for _, filter in pairs(filterButtons) do + if filter and filter:isChecked() ~= filter.default then + filter:setChecked(filter.default) + end + end +end + +local function clearFee() + feeLabel:setText('') + fee = 20 +end + +local function refreshTypeList() + offerTypeList:clearOptions() + offerTypeList:addOption('Buy') + + if Market.isItemSelected() then + if Market.getDepotCount(selectedItem.item.marketData.tradeAs) > 0 then + offerTypeList:addOption('Sell') + end + end +end + +local function addOffer(offer, offerType) + if not offer then + return false + end + local id = offer:getId() + local player = offer:getPlayer() + local amount = offer:getAmount() + local price = offer:getPrice() + local timestamp = offer:getTimeStamp() + local itemName = marketItemNames[offer:getItem():getId()] + if not itemName then + itemName = offer:getItem():getMarketData().name + end + + buyOfferTable:toggleSorting(false) + sellOfferTable:toggleSorting(false) + + buyMyOfferTable:toggleSorting(false) + sellMyOfferTable:toggleSorting(false) + + if amount < 1 then return false end + if offerType == MarketAction.Buy then + if offer.warn then + buyOfferTable:setColumnStyle('OfferTableWarningColumn', true) + end + + local row = nil + if offer.var == MarketRequest.MyOffers then + row = buyMyOfferTable:addRow({ + {text = itemName}, + {text = comma_value(price*amount), sortvalue = price*amount}, + {text = comma_value(price), sortvalue = price}, + {text = amount}, + {text = string.gsub(os.date('%H:%M %d/%m/%y', timestamp), " ", " "), sortvalue = timestamp} + }) + else + row = buyOfferTable:addRow({ + {text = player}, + {text = amount}, + {text = comma_value(price*amount), sortvalue = price*amount}, + {text = comma_value(price), sortvalue = price}, + {text = string.gsub(os.date('%H:%M %d/%m/%y', timestamp), " ", " ")} + }) + end + row.ref = id + + if offer.warn then + row:setTooltip(tr('This offer is 25%% below the average market price')) + buyOfferTable:setColumnStyle('OfferTableColumn', true) + end + else + if offer.warn then + sellOfferTable:setColumnStyle('OfferTableWarningColumn', true) + end + + local row = nil + if offer.var == MarketRequest.MyOffers then + row = sellMyOfferTable:addRow({ + {text = itemName}, + {text = comma_value(price*amount), sortvalue = price*amount}, + {text = comma_value(price), sortvalue = price}, + {text = amount}, + {text = string.gsub(os.date('%H:%M %d/%m/%y', timestamp), " ", " "), sortvalue = timestamp} + }) + else + row = sellOfferTable:addRow({ + {text = player}, + {text = amount}, + {text = comma_value(price*amount), sortvalue = price*amount}, + {text = comma_value(price), sortvalue = price}, + {text = string.gsub(os.date('%H:%M %d/%m/%y', timestamp), " ", " "), sortvalue = timestamp} + }) + end + row.ref = id + + if offer.warn then + row:setTooltip(tr('This offer is 25%% above the average market price')) + sellOfferTable:setColumnStyle('OfferTableColumn', true) + end + end + + buyOfferTable:toggleSorting(false) + sellOfferTable:toggleSorting(false) + buyOfferTable:sort() + sellOfferTable:sort() + + buyMyOfferTable:toggleSorting(false) + sellMyOfferTable:toggleSorting(false) + buyMyOfferTable:sort() + sellMyOfferTable:sort() + + return true +end + +local function mergeOffer(offer) + if not offer then + return false + end + + local id = offer:getId() + local offerType = offer:getType() + local amount = offer:getAmount() + local replaced = false + + if offerType == MarketAction.Buy then + if averagePrice > 0 then + offer.warn = offer:getPrice() <= averagePrice - math.floor(averagePrice / 4) + end + + for i = 1, #marketOffers[MarketAction.Buy] do + local o = marketOffers[MarketAction.Buy][i] + -- replace existing offer + if o:isEqual(id) then + marketOffers[MarketAction.Buy][i] = offer + replaced = true + end + end + if not replaced then + table.insert(marketOffers[MarketAction.Buy], offer) + end + else + if averagePrice > 0 then + offer.warn = offer:getPrice() >= averagePrice + math.floor(averagePrice / 4) + end + + for i = 1, #marketOffers[MarketAction.Sell] do + local o = marketOffers[MarketAction.Sell][i] + -- replace existing offer + if o:isEqual(id) then + marketOffers[MarketAction.Sell][i] = offer + replaced = true + end + end + if not replaced then + table.insert(marketOffers[MarketAction.Sell], offer) + end + end + return true +end + +local function updateOffers(offers) + if not buyOfferTable or not sellOfferTable then + return + end + + balanceLabel:setColor('#bbbbbb') + selectedOffer[MarketAction.Buy] = nil + selectedOffer[MarketAction.Sell] = nil + + selectedMyOffer[MarketAction.Buy] = nil + selectedMyOffer[MarketAction.Sell] = nil + + -- clear existing offer data + buyOfferTable:clearData() + buyOfferTable:setSorting(4, TABLE_SORTING_DESC) + sellOfferTable:clearData() + sellOfferTable:setSorting(4, TABLE_SORTING_ASC) + + sellButton:setEnabled(false) + buyButton:setEnabled(false) + + buyCancelButton:setEnabled(false) + sellCancelButton:setEnabled(false) + + for _, offer in pairs(offers) do + mergeOffer(offer) + end + for type, offers in pairs(marketOffers) do + for i = 1, #offers do + addOffer(offers[i], type) + end + end +end + +local function updateHistoryOffers(offers) + myOfferHistoryTabel:toggleSorting(false) + for _, offer in ipairs(offers) do + local offerType = offer:getType() + local id = offer:getId() + local player = offer:getPlayer() + local amount = offer:getAmount() + local price = offer:getPrice() + local timestamp = offer:getTimeStamp() + local itemName = marketItemNames[offer:getItem():getId()] + if not itemName then + itemName = offer:getItem():getMarketData().name + end + + local offerTypeName = "?" + if offerType == MarketAction.Buy then + offerTypeName = "Buy" + elseif offerType == MarketAction.Sell then + offerTypeName = "Sell" + end + + local row = myOfferHistoryTabel:addRow({ + {text = offerTypeName}, + {text = itemName}, + {text = comma_value(price*amount), sortvalue = price*amount}, + {text = comma_value(price), sortvalue = price}, + {text = amount}, + {text = string.gsub(os.date('%H:%M %d/%m/%y', timestamp), " ", " "), sortvalue = timestamp} + }) + end + myOfferHistoryTabel:toggleSorting(false) + myOfferHistoryTabel:sort() +end + +local function updateDetails(itemId, descriptions, purchaseStats, saleStats) + if not selectedItem then + return + end + + -- update item details + detailsTable:clearData() + for k, desc in pairs(descriptions) do + local columns = { + {text = getMarketDescriptionName(desc[1])..':'}, + {text = desc[2]} + } + detailsTable:addRow(columns) + end + + -- update sale item statistics + sellStatsTable:clearData() + if table.empty(saleStats) then + sellStatsTable:addRow({{text = 'No information'}}) + else + local offerAmount = 0 + local transactions, totalPrice, highestPrice, lowestPrice = 0, 0, 0, 0 + for _, stat in pairs(saleStats) do + if not stat:isNull() then + offerAmount = offerAmount + 1 + transactions = transactions + stat:getTransactions() + totalPrice = totalPrice + stat:getTotalPrice() + local newHigh = stat:getHighestPrice() + if newHigh > highestPrice then + highestPrice = newHigh + end + local newLow = stat:getLowestPrice() + -- ?? getting '0xffffffff' result from lowest price in 9.60 cipsoft + if (lowestPrice == 0 or newLow < lowestPrice) and newLow ~= 0xffffffff then + lowestPrice = newLow + end + end + end + + if offerAmount >= 5 and transactions >= 10 then + averagePrice = math.round(totalPrice / transactions) + else + averagePrice = 0 + end + + sellStatsTable:addRow({{text = 'Total Transations:'}, {text = transactions}}) + sellStatsTable:addRow({{text = 'Highest Price:'}, {text = highestPrice}}) + + if totalPrice > 0 and transactions > 0 then + sellStatsTable:addRow({{text = 'Average Price:'}, + {text = math.floor(totalPrice/transactions)}}) + else + sellStatsTable:addRow({{text = 'Average Price:'}, {text = 0}}) + end + + sellStatsTable:addRow({{text = 'Lowest Price:'}, {text = lowestPrice}}) + end + + -- update buy item statistics + buyStatsTable:clearData() + if table.empty(purchaseStats) then + buyStatsTable:addRow({{text = 'No information'}}) + else + local transactions, totalPrice, highestPrice, lowestPrice = 0, 0, 0, 0 + for _, stat in pairs(purchaseStats) do + if not stat:isNull() then + transactions = transactions + stat:getTransactions() + totalPrice = totalPrice + stat:getTotalPrice() + local newHigh = stat:getHighestPrice() + if newHigh > highestPrice then + highestPrice = newHigh + end + local newLow = stat:getLowestPrice() + -- ?? getting '0xffffffff' result from lowest price in 9.60 cipsoft + if (lowestPrice == 0 or newLow < lowestPrice) and newLow ~= 0xffffffff then + lowestPrice = newLow + end + end + end + + buyStatsTable:addRow({{text = 'Total Transations:'},{text = transactions}}) + buyStatsTable:addRow({{text = 'Highest Price:'}, {text = highestPrice}}) + + if totalPrice > 0 and transactions > 0 then + buyStatsTable:addRow({{text = 'Average Price:'}, + {text = math.floor(totalPrice/transactions)}}) + else + buyStatsTable:addRow({{text = 'Average Price:'}, {text = 0}}) + end + + buyStatsTable:addRow({{text = 'Lowest Price:'}, {text = lowestPrice}}) + end +end + +local function updateSelectedItem(widget) + selectedItem.item = widget.item + selectedItem.ref = widget + + Market.resetCreateOffer() + if Market.isItemSelected() then + selectedItem:setItem(selectedItem.item.displayItem) + nameLabel:setText(selectedItem.item.marketData.name) + clearOffers() + + Market.enableCreateOffer(true) -- update offer types + MarketProtocol.sendMarketBrowse(selectedItem.item.marketData.tradeAs) -- send browsed msg + else + Market.clearSelectedItem() + end +end + +local function updateBalance(balance) + local balance = tonumber(balance) + if not balance then + return + end + + if balance < 0 then balance = 0 end + information.balance = balance + + balanceLabel:setText('Balance: '.. comma_value(balance) ..' gold') + balanceLabel:resizeToText() +end + +local function updateFee(price, amount) + fee = math.ceil(price / 100 * amount) + if fee < 20 then + fee = 20 + elseif fee > 1000 then + fee = 1000 + end + feeLabel:setText('Fee: '.. comma_value(fee)) + feeLabel:resizeToText() +end + +local function destroyAmountWindow() + if amountWindow then + amountWindow:destroy() + amountWindow = nil + end +end + +local function cancelMyOffer(actionType) + local offer = selectedMyOffer[actionType] + MarketProtocol.sendMarketCancelOffer(offer:getTimeStamp(), offer:getCounter()) + Market.refreshMyOffers() +end + +local function openAmountWindow(callback, actionType, actionText) + if not Market.isOfferSelected(actionType) then + return + end + + amountWindow = g_ui.createWidget('AmountWindow', rootWidget) + amountWindow:lock() + + local offer = selectedOffer[actionType] + local item = offer:getItem() + + local maximum = offer:getAmount() + if actionType == MarketAction.Sell then + local depot = Market.getDepotCount(item:getId()) + if maximum > depot then + maximum = depot + end + else + maximum = math.min(maximum, math.floor(information.balance / offer:getPrice())) + end + + if item:isStackable() then + maximum = math.min(maximum, MarketMaxAmountStackable) + else + maximum = math.min(maximum, MarketMaxAmount) + end + + local itembox = amountWindow:getChildById('item') + itembox:setItemId(item:getId()) + + local scrollbar = amountWindow:getChildById('amountScrollBar') + scrollbar:setText(comma_value(offer:getPrice()) ..'gp') + + scrollbar.onValueChange = function(widget, value) + widget:setText(comma_value(value*offer:getPrice())..'gp') + itembox:setText(comma_value(value)) + end + scrollbar:setRange(1, maximum) + scrollbar:setValue(1) + + local okButton = amountWindow:getChildById('buttonOk') + if actionText then + okButton:setText(actionText) + end + + local okFunc = function() + local counter = offer:getCounter() + local timestamp = offer:getTimeStamp() + callback(scrollbar:getValue(), timestamp, counter) + destroyAmountWindow() + end + + local cancelButton = amountWindow:getChildById('buttonCancel') + local cancelFunc = function() + destroyAmountWindow() + end + + amountWindow.onEnter = okFunc + amountWindow.onEscape = cancelFunc + + okButton.onClick = okFunc + cancelButton.onClick = cancelFunc +end + +local function onSelectSellOffer(table, selectedRow, previousSelectedRow) + updateBalance() + for _, offer in pairs(marketOffers[MarketAction.Sell]) do + if offer:isEqual(selectedRow.ref) then + selectedOffer[MarketAction.Buy] = offer + end + end + + local offer = selectedOffer[MarketAction.Buy] + if offer then + local price = offer:getPrice() + if price > information.balance then + balanceLabel:setColor('#b22222') -- red + buyButton:setEnabled(false) + else + local slice = (information.balance / 2) + if (price/slice) * 100 <= 40 then + color = '#008b00' -- green + elseif (price/slice) * 100 <= 70 then + color = '#eec900' -- yellow + else + color = '#ee9a00' -- orange + end + balanceLabel:setColor(color) + buyButton:setEnabled(true) + end + end +end + +local function onSelectBuyOffer(table, selectedRow, previousSelectedRow) + updateBalance() + for _, offer in pairs(marketOffers[MarketAction.Buy]) do + if offer:isEqual(selectedRow.ref) then + selectedOffer[MarketAction.Sell] = offer + if Market.getDepotCount(offer:getItem():getId()) > 0 then + sellButton:setEnabled(true) + else + sellButton:setEnabled(false) + end + end + end +end + +local function onSelectMyBuyOffer(table, selectedRow, previousSelectedRow) + for _, offer in pairs(marketOffers[MarketAction.Buy]) do + if offer:isEqual(selectedRow.ref) then + selectedMyOffer[MarketAction.Buy] = offer + buyCancelButton:setEnabled(true) + end + end +end + +local function onSelectMySellOffer(table, selectedRow, previousSelectedRow) + for _, offer in pairs(marketOffers[MarketAction.Sell]) do + if offer:isEqual(selectedRow.ref) then + selectedMyOffer[MarketAction.Sell] = offer + sellCancelButton:setEnabled(true) + end + end +end + +local function onChangeCategory(combobox, option) + local id = getMarketCategoryId(option) + if id == MarketCategory.MetaWeapons then + -- enable and load weapons filter/items + subCategoryList:setEnabled(true) + slotFilterList:setEnabled(true) + local subId = getMarketCategoryId(subCategoryList:getCurrentOption().text) + Market.loadMarketItems(subId) + else + subCategoryList:setEnabled(false) + slotFilterList:setEnabled(false) + Market.loadMarketItems(id) -- load standard filter + end +end + +local function onChangeSubCategory(combobox, option) + Market.loadMarketItems(getMarketCategoryId(option)) + slotFilterList:clearOptions() + + local subId = getMarketCategoryId(subCategoryList:getCurrentOption().text) + local slots = MarketCategoryWeapons[subId].slots + for _, slot in pairs(slots) do + if table.haskey(MarketSlotFilters, slot) then + slotFilterList:addOption(MarketSlotFilters[slot]) + end + end + slotFilterList:setEnabled(true) +end + +local function onChangeSlotFilter(combobox, option) + Market.updateCurrentItems() +end + +local function onChangeOfferType(combobox, option) + local item = selectedItem.item + local maximum = item.thingType:isStackable() and MarketMaxAmountStackable or MarketMaxAmount + + if option == 'Sell' then + maximum = math.min(maximum, Market.getDepotCount(item.marketData.tradeAs)) + amountEdit:setMaximum(maximum) + else + amountEdit:setMaximum(maximum) + end +end + +local function onTotalPriceChange() + local amount = amountEdit:getValue() + local totalPrice = totalPriceEdit:getValue() + local piecePrice = math.floor(totalPrice/amount) + + piecePriceEdit:setValue(piecePrice, true) + if Market.isItemSelected() then + updateFee(piecePrice, amount) + end +end + +local function onPiecePriceChange() + local amount = amountEdit:getValue() + local totalPrice = totalPriceEdit:getValue() + local piecePrice = piecePriceEdit:getValue() + + totalPriceEdit:setValue(piecePrice*amount, true) + if Market.isItemSelected() then + updateFee(piecePrice, amount) + end +end + +local function onAmountChange() + local amount = amountEdit:getValue() + local piecePrice = piecePriceEdit:getValue() + local totalPrice = piecePrice * amount + + totalPriceEdit:setValue(piecePrice*amount, true) + if Market.isItemSelected() then + updateFee(piecePrice, amount) + end +end + +local function onMarketMessage(messageMode, message) + Market.displayMessage(message) +end + +local function initMarketItems(items) + for c = MarketCategory.First, MarketCategory.Last do + marketItems[c] = {} + end + marketItemNames = {} + + -- save a list of items which are already added + local itemSet = {} + + -- parse items send by server + if items then + for _, entry in ipairs(items) do + local item = Item.create(entry.id) + local thingType = g_things.getThingType(entry.id, ThingCategoryItem) + if item and thingType and not marketItemNames[entry.id] then + -- create new marketItem block + local marketItem = { + displayItem = item, + thingType = thingType, + marketData = { + name = entry.name, + category = entry.category, + requiredLevel = 0, + restrictVocation = 0, + showAs = entry.id, + tradeAs = entry.id + } + } + + -- add new market item + if marketItems[entry.category] ~= nil then + table.insert(marketItems[entry.category], marketItem) + marketItemNames[entry.id] = entry.name + end + end + end + Market.updateCategories() + return + end + + -- populate all market items + local types = g_things.findThingTypeByAttr(ThingAttrMarket, 0) + for i = 1, #types do + local itemType = types[i] + + local item = Item.create(itemType:getId()) + if item then + local marketData = itemType:getMarketData() + if not table.empty(marketData) and not itemSet[marketData.tradeAs] then + -- Some items use a different sprite in Market + item:setId(marketData.showAs) + + -- create new marketItem block + local marketItem = { + displayItem = item, + thingType = itemType, + marketData = marketData + } + + -- add new market item + if marketItems[marketData.category] ~= nil then + table.insert(marketItems[marketData.category], marketItem) + itemSet[marketData.tradeAs] = true + end + end + end + end +end + +local function initInterface() + -- TODO: clean this up + -- setup main tabs + mainTabBar = marketWindow:getChildById('mainTabBar') + mainTabBar:setContentWidget(marketWindow:getChildById('mainTabContent')) + + -- setup 'Market Offer' section tabs + marketOffersPanel = g_ui.loadUI('ui/marketoffers') + mainTabBar:addTab(tr('Market Offers'), marketOffersPanel) + + selectionTabBar = marketOffersPanel:getChildById('leftTabBar') + selectionTabBar:setContentWidget(marketOffersPanel:getChildById('leftTabContent')) + + browsePanel = g_ui.loadUI('ui/marketoffers/browse') + selectionTabBar:addTab(tr('Browse'), browsePanel) + + -- Currently not used + -- "Reserved for more functionality later" + --overviewPanel = g_ui.loadUI('ui/marketoffers/overview') + --selectionTabBar:addTab(tr('Overview'), overviewPanel) + + displaysTabBar = marketOffersPanel:getChildById('rightTabBar') + displaysTabBar:setContentWidget(marketOffersPanel:getChildById('rightTabContent')) + + itemStatsPanel = g_ui.loadUI('ui/marketoffers/itemstats') + displaysTabBar:addTab(tr('Statistics'), itemStatsPanel) + + itemDetailsPanel = g_ui.loadUI('ui/marketoffers/itemdetails') + displaysTabBar:addTab(tr('Details'), itemDetailsPanel) + + itemOffersPanel = g_ui.loadUI('ui/marketoffers/itemoffers') + displaysTabBar:addTab(tr('Offers'), itemOffersPanel) + displaysTabBar:selectTab(displaysTabBar:getTab(tr('Offers'))) + + -- setup 'My Offer' section tabs + myOffersPanel = g_ui.loadUI('ui/myoffers') + local myOffersTab = mainTabBar:addTab(tr('My Offers'), myOffersPanel) + + offersTabBar = myOffersPanel:getChildById('offersTabBar') + offersTabBar:setContentWidget(myOffersPanel:getChildById('offersTabContent')) + + currentOffersPanel = g_ui.loadUI('ui/myoffers/currentoffers') + myCurrentOffersTab = offersTabBar:addTab(tr('Current Offers'), currentOffersPanel) + + offerHistoryPanel = g_ui.loadUI('ui/myoffers/offerhistory') + myOfferHistoryTab = offersTabBar:addTab(tr('Offer History'), offerHistoryPanel) + + balanceLabel = marketWindow:getChildById('balanceLabel') + + mainTabBar.onTabChange = function(widget, tab) + if tab == myOffersTab then + local ctab = offersTabBar:getCurrentTab() + if ctab == myCurrentOffersTab then + Market.refreshMyOffers() + elseif ctab == myOfferHistoryTab then + Market.refreshMyOffersHistory() + end + else + Market.refreshOffers() + end + end + + offersTabBar.onTabChange = function(widget, tab) + if tab == myCurrentOffersTab then + Market.refreshMyOffers() + elseif tab == myOfferHistoryTab then + Market.refreshMyOffersHistory() + end + end + + -- setup offers + buyButton = itemOffersPanel:getChildById('buyButton') + buyButton.onClick = function() openAmountWindow(Market.acceptMarketOffer, MarketAction.Buy, 'Buy') end + + sellButton = itemOffersPanel:getChildById('sellButton') + sellButton.onClick = function() openAmountWindow(Market.acceptMarketOffer, MarketAction.Sell, 'Sell') end + + -- setup selected item + nameLabel = marketOffersPanel:getChildById('nameLabel') + selectedItem = marketOffersPanel:getChildById('selectedItem') + + -- setup create new offer + totalPriceEdit = marketOffersPanel:getChildById('totalPriceEdit') + piecePriceEdit = marketOffersPanel:getChildById('piecePriceEdit') + amountEdit = marketOffersPanel:getChildById('amountEdit') + feeLabel = marketOffersPanel:getChildById('feeLabel') + totalPriceEdit.onValueChange = onTotalPriceChange + piecePriceEdit.onValueChange = onPiecePriceChange + amountEdit.onValueChange = onAmountChange + + offerTypeList = marketOffersPanel:getChildById('offerTypeComboBox') + offerTypeList.onOptionChange = onChangeOfferType + + anonymous = marketOffersPanel:getChildById('anonymousCheckBox') + createOfferButton = marketOffersPanel:getChildById('createOfferButton') + createOfferButton.onClick = Market.createNewOffer + Market.enableCreateOffer(false) + + -- setup filters + filterButtons[MarketFilters.Vocation] = browsePanel:getChildById('filterVocation') + filterButtons[MarketFilters.Level] = browsePanel:getChildById('filterLevel') + filterButtons[MarketFilters.Depot] = browsePanel:getChildById('filterDepot') + filterButtons[MarketFilters.SearchAll] = browsePanel:getChildById('filterSearchAll') + + -- set filter default values + clearFilters() + + -- hook filters + for _, filter in pairs(filterButtons) do + filter.onCheckChange = Market.updateCurrentItems + end + + searchEdit = browsePanel:getChildById('searchEdit') + categoryList = browsePanel:getChildById('categoryComboBox') + subCategoryList = browsePanel:getChildById('subCategoryComboBox') + slotFilterList = browsePanel:getChildById('slotComboBox') + + slotFilterList:addOption(MarketSlotFilters[255]) + slotFilterList:setEnabled(false) + + Market.updateCategories() + + -- hook item filters + categoryList.onOptionChange = onChangeCategory + subCategoryList.onOptionChange = onChangeSubCategory + slotFilterList.onOptionChange = onChangeSlotFilter + + -- setup tables + buyOfferTable = itemOffersPanel:recursiveGetChildById('buyingTable') + sellOfferTable = itemOffersPanel:recursiveGetChildById('sellingTable') + detailsTable = itemDetailsPanel:recursiveGetChildById('detailsTable') + buyStatsTable = itemStatsPanel:recursiveGetChildById('buyStatsTable') + sellStatsTable = itemStatsPanel:recursiveGetChildById('sellStatsTable') + buyOfferTable.onSelectionChange = onSelectBuyOffer + sellOfferTable.onSelectionChange = onSelectSellOffer + + -- setup my offers + buyMyOfferTable = currentOffersPanel:recursiveGetChildById('myBuyingTable') + sellMyOfferTable = currentOffersPanel:recursiveGetChildById('mySellingTable') + myOfferHistoryTabel = offerHistoryPanel:recursiveGetChildById('myHistoryTable') + + buyMyOfferTable.onSelectionChange = onSelectMyBuyOffer + sellMyOfferTable.onSelectionChange = onSelectMySellOffer + + buyCancelButton = currentOffersPanel:getChildById('buyCancelButton') + buyCancelButton.onClick = function() cancelMyOffer(MarketAction.Buy) end + + sellCancelButton = currentOffersPanel:getChildById('sellCancelButton') + sellCancelButton.onClick = function() cancelMyOffer(MarketAction.Sell) end + + + buyStatsTable:setColumnWidth({120, 270}) + sellStatsTable:setColumnWidth({120, 270}) + detailsTable:setColumnWidth({80, 330}) + + buyOfferTable:setSorting(4, TABLE_SORTING_DESC) + sellOfferTable:setSorting(4, TABLE_SORTING_ASC) + + buyMyOfferTable:setSorting(3, TABLE_SORTING_DESC) + sellMyOfferTable:setSorting(3, TABLE_SORTING_DESC) + myOfferHistoryTabel:setSorting(6, TABLE_SORTING_DESC) +end + +function init() + g_ui.importStyle('market') + g_ui.importStyle('ui/general/markettabs') + g_ui.importStyle('ui/general/marketbuttons') + g_ui.importStyle('ui/general/marketcombobox') + g_ui.importStyle('ui/general/amountwindow') + + offerExhaust[MarketAction.Sell] = 10 + offerExhaust[MarketAction.Buy] = 20 + + registerMessageMode(MessageModes.Market, onMarketMessage) + + protocol.initProtocol() + connect(g_game, { onGameEnd = Market.reset }) + connect(g_game, { onGameEnd = Market.close }) + connect(g_game, { onGameStart = Market.updateCategories }) + connect(g_game, { onCoinBalance = Market.onCoinBalance }) + + marketWindow = g_ui.createWidget('MarketWindow', rootWidget) + marketWindow:hide() + + initInterface() -- build interface +end + +function terminate() + Market.close() + + unregisterMessageMode(MessageModes.Market, onMarketMessage) + + protocol.terminateProtocol() + disconnect(g_game, { onGameEnd = Market.reset }) + disconnect(g_game, { onGameEnd = Market.close }) + disconnect(g_game, { onGameStart = Market.updateCategories }) + disconnect(g_game, { onCoinBalance = Market.onCoinBalance }) + + destroyAmountWindow() + marketWindow:destroy() + + Market = nil +end + +function Market.reset() + balanceLabel:setColor('#bbbbbb') + categoryList:setCurrentOption(getMarketCategoryName(MarketCategory.First)) + searchEdit:setText('') + clearFilters() + clearMyOffers() + if not table.empty(information) then + Market.updateCurrentItems() + end +end + +function Market.updateCategories() + categoryList:clearOptions() + subCategoryList:clearOptions() + + local categories = {} + local addedCategories = {} + for _, c in ipairs(g_things.getMarketCategories()) do + table.insert(categories, getMarketCategoryName(c) or "Unknown") + addedCategories[c] = true + end + for c, items in ipairs(marketItems) do + if #items > 0 and not addedCategories[c] then + table.insert(categories, getMarketCategoryName(c) or "Unknown") + addedCategories[c] = true + end + end + + table.sort(categories) + for _, c in ipairs(categories) do + categoryList:addOption(c) + end + + for i = MarketCategory.Ammunition, MarketCategory.WandsRods do + subCategoryList:addOption(getMarketCategoryName(i)) + end + + categoryList:addOption(getMarketCategoryName(255)) -- meta weapons + categoryList:setCurrentOption(getMarketCategoryName(MarketCategory.First)) + subCategoryList:setEnabled(false) +end + +function Market.displayMessage(message) + if marketWindow:isHidden() then return end + + local infoBox = displayInfoBox(tr('Market Error'), message) + infoBox:lock() +end + +function Market.clearSelectedItem() + if Market.isItemSelected() then + Market.resetCreateOffer(true) + offerTypeList:clearOptions() + offerTypeList:setText('Please Select') + offerTypeList:setEnabled(false) + + clearOffers() + radioItemSet:selectWidget(nil) + nameLabel:setText('No item selected.') + selectedItem:setItem(nil) + selectedItem.item = nil + selectedItem.ref:setChecked(false) + selectedItem.ref = nil + + detailsTable:clearData() + buyStatsTable:clearData() + sellStatsTable:clearData() + + Market.enableCreateOffer(false) + end +end + +function Market.isItemSelected() + return selectedItem and selectedItem.item +end + +function Market.isOfferSelected(type) + return selectedOffer[type] and not selectedOffer[type]:isNull() +end + +function Market.getDepotCount(itemId) + if not information.depotItems then + return 0 + end + return information.depotItems[itemId] or 0 +end + +function Market.enableCreateOffer(enable) + offerTypeList:setEnabled(enable) + totalPriceEdit:setEnabled(enable) + piecePriceEdit:setEnabled(enable) + amountEdit:setEnabled(enable) + anonymous:setEnabled(enable) + createOfferButton:setEnabled(enable) + + local prevAmountButton = marketOffersPanel:recursiveGetChildById('prevAmountButton') + local nextAmountButton = marketOffersPanel:recursiveGetChildById('nextAmountButton') + + prevAmountButton:setEnabled(enable) + nextAmountButton:setEnabled(enable) +end + +function Market.close(notify) + if notify == nil then notify = true end + if not marketWindow:isHidden() then + marketWindow:hide() + marketWindow:unlock() + modules.game_interface.getRootPanel():focus() + Market.clearSelectedItem() + Market.reset() + if notify then + MarketProtocol.sendMarketLeave() + end + end +end + +function Market.incrementAmount() + amountEdit:setValue(amountEdit:getValue() + 1) +end + +function Market.decrementAmount() + amountEdit:setValue(amountEdit:getValue() - 1) +end + +function Market.updateCurrentItems() + if not categoryList or not categoryList:getCurrentOption() then + return + end + local id = getMarketCategoryId(categoryList:getCurrentOption().text) + if id == MarketCategory.MetaWeapons then + id = getMarketCategoryId(subCategoryList:getCurrentOption().text) + end + Market.loadMarketItems(id) +end + +function Market.resetCreateOffer(resetFee) + piecePriceEdit:setValue(1) + totalPriceEdit:setValue(1) + amountEdit:setValue(1) + refreshTypeList() + + if resetFee then + clearFee() + else + updateFee(0, 0) + end +end + +function Market.refreshItemsWidget(selectItem) + local selectItem = selectItem or 0 + itemsPanel = browsePanel:recursiveGetChildById('itemsPanel') + + local layout = itemsPanel:getLayout() + layout:disableUpdates() + + Market.clearSelectedItem() + itemsPanel:destroyChildren() + + if radioItemSet then + radioItemSet:destroy() + end + radioItemSet = UIRadioGroup.create() + + local select = nil + for i = 1, #currentItems do + local item = currentItems[i] + local itemBox = g_ui.createWidget('MarketItemBox', itemsPanel) + itemBox.onCheckChange = Market.onItemBoxChecked + itemBox.item = item + + if selectItem > 0 and item.marketData.tradeAs == selectItem then + select = itemBox + selectItem = 0 + end + + local itemWidget = itemBox:getChildById('item') + itemWidget:setItem(item.displayItem) + + local amount = Market.getDepotCount(item.marketData.tradeAs) + if amount > 0 then + itemWidget:setText(comma_value(amount)) + itemBox:setTooltip('You have '.. amount ..' in your depot.') + end + + radioItemSet:addWidget(itemBox) + end + + if select then + radioItemSet:selectWidget(select, false) + end + + layout:enableUpdates() + layout:update() +end + +function Market.refreshOffers() + if Market.isItemSelected() then + Market.onItemBoxChecked(selectedItem.ref) + else + local ctab = offersTabBar:getCurrentTab() + if ctab == myCurrentOffersTab then + Market.refreshMyOffers() + elseif ctab == myOfferHistoryTab then + Market.refreshMyOffersHistory() + end + end +end + +function Market.refreshMyOffers() + clearMyOffers() + MarketProtocol.sendMarketBrowseMyOffers() +end + +function Market.refreshMyOffersHistory() + clearMyOffers() + MarketProtocol.sendMarketBrowseMyHistory() +end + + +function Market.loadMarketItems(category) + clearItems() + + -- check search filter + local searchFilter = searchEdit:getText() + if searchFilter and searchFilter:len() > 2 then + if filterButtons[MarketFilters.SearchAll]:isChecked() then + category = MarketCategory.All + end + end + + if not marketItems[category] and category ~= MarketCategory.All then + return + end + + if category == MarketCategory.All then + -- loop all categories + for category = MarketCategory.First, MarketCategory.Last do + if marketItems[category] then + for i = 1, #marketItems[category] do + local item = marketItems[category][i] + if isItemValid(item, category, searchFilter) then + table.insert(currentItems, item) + end + end + end + end + else + -- loop specific category + for i = 1, #marketItems[category] do + local item = marketItems[category][i] + if isItemValid(item, category, searchFilter) then + table.insert(currentItems, item) + end + end + end + + Market.refreshItemsWidget() +end + +function Market.createNewOffer() + local type = offerTypeList:getCurrentOption().text + if type == 'Sell' then + type = MarketAction.Sell + else + type = MarketAction.Buy + end + + if not Market.isItemSelected() then + return + end + + local spriteId = selectedItem.item.marketData.tradeAs + + local piecePrice = piecePriceEdit:getValue() + local amount = amountEdit:getValue() + local anonymous = anonymous:isChecked() and 1 or 0 + + -- error checking + local errorMsg = '' + if type == MarketAction.Buy then + if information.balance < ((piecePrice * amount) + fee) then + errorMsg = errorMsg..'Not enough balance to create this offer.\n' + end + elseif type == MarketAction.Sell then + if information.balance < fee then + errorMsg = errorMsg..'Not enough balance to create this offer.\n' + end + if Market.getDepotCount(spriteId) < amount then + errorMsg = errorMsg..'Not enough items in your depot to create this offer.\n' + end + end + + if piecePrice > piecePriceEdit.maximum then + errorMsg = errorMsg..'Price is too high.\n' + elseif piecePrice < piecePriceEdit.minimum then + errorMsg = errorMsg..'Price is too low.\n' + end + + if amount > amountEdit.maximum then + errorMsg = errorMsg..'Amount is too high.\n' + elseif amount < amountEdit.minimum then + errorMsg = errorMsg..'Amount is too low.\n' + end + + if amount * piecePrice > MarketMaxPrice then + errorMsg = errorMsg..'Total price is too high.\n' + end + + if information.totalOffers >= MarketMaxOffers then + errorMsg = errorMsg..'You cannot create more offers.\n' + end + + local timeCheck = os.time() - lastCreatedOffer + if timeCheck < offerExhaust[type] then + local waitTime = math.ceil(offerExhaust[type] - timeCheck) + errorMsg = errorMsg..'You must wait '.. waitTime ..' seconds before creating a new offer.\n' + end + + if errorMsg ~= '' then + Market.displayMessage(errorMsg) + return + end + + MarketProtocol.sendMarketCreateOffer(type, spriteId, amount, piecePrice, anonymous) + lastCreatedOffer = os.time() + Market.resetCreateOffer() +end + +function Market.acceptMarketOffer(amount, timestamp, counter) + if timestamp > 0 and amount > 0 then + MarketProtocol.sendMarketAcceptOffer(timestamp, counter, amount) + Market.refreshOffers() + end +end + +function Market.onItemBoxChecked(widget) + if widget:isChecked() then + updateSelectedItem(widget) + end +end + +-- protocol callback functions + +function Market.onMarketEnter(depotItems, offers, balance, vocation, items) + if not loaded or (items and #items > 0) then + initMarketItems(items) + loaded = true + end + + updateBalance(balance) + averagePrice = 0 + + information.totalOffers = offers + local player = g_game.getLocalPlayer() + if player then + information.player = player + end + if vocation == -1 then + if player then + information.vocation = player:getVocation() + end + else + -- vocation must be compatible with < 950 + information.vocation = vocation + end + + -- set list of depot items + information.depotItems = depotItems + + for i = 1, #marketItems[MarketCategory.TibiaCoins] do + local item = marketItems[MarketCategory.TibiaCoins][i].displayItem + depotItems[item:getId()] = tibiaCoins + end + + -- update the items widget to match depot items + if Market.isItemSelected() then + local spriteId = selectedItem.item.marketData.tradeAs + MarketProtocol.silent(true) -- disable protocol messages + Market.refreshItemsWidget(spriteId) + MarketProtocol.silent(false) -- enable protocol messages + else + Market.refreshItemsWidget() + end + + if table.empty(currentItems) then + Market.loadMarketItems(MarketCategory.First) + end + + if g_game.isOnline() then + -- marketWindow:lock() + marketWindow:show() + end +end + +function Market.onMarketLeave() + Market.close(false) +end + +function Market.onMarketDetail(itemId, descriptions, purchaseStats, saleStats) + updateDetails(itemId, descriptions, purchaseStats, saleStats) +end + +function Market.onMarketBrowse(offers, offersType) + if offersType == MarketRequest.MyHistory then + updateHistoryOffers(offers) + else + updateOffers(offers) + end +end + +function Market.onCoinBalance(coins, transferableCoins) + tibiaCoins = coins + if not marketItems[MarketCategory.TibiaCoins] then return end + for i = 1, #marketItems[MarketCategory.TibiaCoins] do + local item = marketItems[MarketCategory.TibiaCoins][i].displayItem + information.depotItems[item:getId()] = tibiaCoins + end +end diff --git a/800OTClient/modules/game_market/market.otmod b/800OTClient/modules/game_market/market.otmod new file mode 100644 index 0000000..4d6248a --- /dev/null +++ b/800OTClient/modules/game_market/market.otmod @@ -0,0 +1,9 @@ +Module + name: game_market + description: Global item market system + author: BeniS + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ offerstatistic, marketoffer, marketprotocol, market ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_market/market.otui b/800OTClient/modules/game_market/market.otui new file mode 100644 index 0000000..ae45044 --- /dev/null +++ b/800OTClient/modules/game_market/market.otui @@ -0,0 +1,62 @@ +MarketWindow < MainWindow + id: marketWindow + !text: tr('Market') + size: 700 530 + + @onEscape: Market.close() + + // Main Panel Window + + MarketTabBar + id: mainTabBar + width: 164 + height: 25 + anchors.top: parent.top + anchors.left: parent.left + + Panel + id: mainTabContent + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + padding: 3 + border-width: 1 + border-color: #000000 + margin-bottom: 20 + + Label + id: balanceLabel + !text: tr('Balance') .. ':' + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: parent.top + anchors.right: parent.right + + Button + id: closeButton + !text: tr('Close') + anchors.top: mainTabContent.bottom + anchors.horizontalCenter: mainTabContent.horizontalCenter + margin-top: 5 + width: 110 + @onClick: Market.close() + + Button + id: refreshOffersButton + !text: tr('Refresh Offers') + anchors.top: mainTabContent.bottom + anchors.right: mainTabContent.right + margin-top: 5 + width: 110 + @onClick: Market.refreshOffers() + + Button + id: resetButton + !text: tr('Reset Market') + !tooltip: tr('Reset selection, filters & search') + anchors.top: mainTabContent.bottom + anchors.left: mainTabContent.left + margin-top: 5 + width: 110 + @onClick: Market.reset() \ No newline at end of file diff --git a/800OTClient/modules/game_market/marketoffer.lua b/800OTClient/modules/game_market/marketoffer.lua new file mode 100644 index 0000000..b2dcbfa --- /dev/null +++ b/800OTClient/modules/game_market/marketoffer.lua @@ -0,0 +1,158 @@ +MarketOffer = {} +MarketOffer.__index = MarketOffer + +local OFFER_TIMESTAMP = 1 +local OFFER_COUNTER = 2 + +MarketOffer.new = function(offerId, t, item, amount, price, playerName, state, var) + local offer = { + id = {}, + type = nil, + item = 0, + amount = 0, + price = 0, + player = '', + state = 0, + var = nil + } + + if not offerId or type(offerId) ~= 'table' then + g_logger.error('MarketOffer.new - invalid offer id provided.') + end + offer.id = offerId + + t = tonumber(t) + if t ~= MarketAction.Buy and t ~= MarketAction.Sell then + g_logger.error('MarketOffer.new - invalid type provided.') + end + offer.type = t + + if not item then + g_logger.error('MarketOffer.new - invalid item provided.') + end + offer.item = item + + offer.amount = amount + offer.price = price + offer.player = playerName + + state = tonumber(state) + if state ~= MarketOfferState.Active and state ~= MarketOfferState.Cancelled + and state ~= MarketOfferState.Expired and state ~= MarketOfferState.Accepted then + g_logger.error('MarketOffer.new - invalid state provided.') + end + offer.state = state + offer.var = var + + setmetatable(offer, MarketOffer) + return offer +end + +function MarketOffer:isEqual(id) + return self.id[OFFER_TIMESTAMP] == id[OFFER_TIMESTAMP] and self.id[OFFER_COUNTER] == id[OFFER_COUNTER] +end + +function MarketOffer:isLessThan(id) + return self.id[OFFER_TIMESTAMP] <= id[OFFER_TIMESTAMP] and self.id[OFFER_COUNTER] < id[OFFER_COUNTER] +end + +function MarketOffer:isNull() + return table.empty(self.id) +end + +-- Sets/Gets + +function MarketOffer:setId(id) + if not id or type(id) ~= 'table' then + g_logger.error('MarketOffer.setId - invalid id provided.') + end + self.id = id +end + +function MarketOffer:getId() + return self.id +end + +function MarketOffer:setType(t) + if not t or type(t) ~= 'number' then + g_logger.error('MarketOffer.setItem - invalid type provided.') + end + self.type = type +end + +function MarketOffer:getType() + return self.type +end + +function MarketOffer:setItem(item) + if not item or type(item) ~= 'userdata' then + g_logger.error('MarketOffer.setItem - invalid item id provided.') + end + self.item = item +end + +function MarketOffer:getItem() + return self.item +end + +function MarketOffer:setAmount(amount) + if not amount or type(amount) ~= 'number' then + g_logger.error('MarketOffer.setAmount - invalid amount provided.') + end + self.amount = amount +end + +function MarketOffer:getAmount() + return self.amount +end + +function MarketOffer:setPrice(price) + if not price or type(price) ~= 'number' then + g_logger.error('MarketOffer.setPrice - invalid price provided.') + end + self.price = price +end + +function MarketOffer:getPrice() + return self.price +end + +function MarketOffer:getTotalPrice() + return self.price * self.amount +end + +function MarketOffer:setPlayer(player) + if not player or type(player) ~= 'number' then + g_logger.error('MarketOffer.setPlayer - invalid player provided.') + end + self.player = player +end + +function MarketOffer:getPlayer() + return self.player +end + +function MarketOffer:setState(state) + if not state or type(state) ~= 'number' then + g_logger.error('MarketOffer.setState - invalid state provided.') + end + self.state = state +end + +function MarketOffer:getState() + return self.state +end + +function MarketOffer:getTimeStamp() + if table.empty(self.id) or #self.id < OFFER_TIMESTAMP then + return + end + return self.id[OFFER_TIMESTAMP] +end + +function MarketOffer:getCounter() + if table.empty(self.id) or #self.id < OFFER_COUNTER then + return + end + return self.id[OFFER_COUNTER] +end diff --git a/800OTClient/modules/game_market/marketprotocol.lua b/800OTClient/modules/game_market/marketprotocol.lua new file mode 100644 index 0000000..ab4a673 --- /dev/null +++ b/800OTClient/modules/game_market/marketprotocol.lua @@ -0,0 +1,278 @@ +MarketProtocol = {} + +-- private functions + +local silent +local protocol +local statistics = runinsandbox('offerstatistic') + +local function send(msg) + if protocol and not silent then + protocol:send(msg) + end +end + +local function readMarketOffer(msg, action, var) + local timestamp = msg:getU32() + local counter = msg:getU16() + + local itemId = 0 + if var == MarketRequest.MyOffers or var == MarketRequest.MyHistory then + itemId = msg:getU16() + else + itemId = var + end + + local amount = msg:getU16() + local price = msg:getU32() + local playerName + local state = MarketOfferState.Active + if var == MarketRequest.MyHistory then + state = msg:getU8() + elseif var == MarketRequest.MyOffers then + else + playerName = msg:getString() + end + + return MarketOffer.new({timestamp, counter}, action, Item.create(itemId), amount, price, playerName, state, var) +end + +-- parsing protocols +local function parseMarketEnter(protocol, msg) + local items + if g_game.getClientVersion() < 944 then + items = {} + local itemsCount = msg:getU16() + for i = 1, itemsCount do + local itemId = msg:getU16() + local category = msg:getU8() + local name = msg:getString() + table.insert(items, { + id = itemId, + category = category, + name = name + }) + end + end + + local balance = 0 + if g_game.getProtocolVersion() <= 1250 or not g_game.getFeature(GameTibia12Protocol) then + if g_game.getProtocolVersion() >= 981 or g_game.getProtocolVersion() < 944 then + balance = msg:getU64() + else + balance = msg:getU32() + end + end + + local vocation = -1 + if g_game.getProtocolVersion() >= 944 and g_game.getProtocolVersion() < 950 then + vocation = msg:getU8() -- get vocation id + end + local offers = msg:getU8() + + local depotItems = {} + local depotCount = msg:getU16() + for i = 1, depotCount do + local itemId = msg:getU16() -- item id + local itemCount = msg:getU16() -- item count + + depotItems[itemId] = itemCount + end + + signalcall(Market.onMarketEnter, depotItems, offers, balance, vocation, items) + return true +end + +local function parseMarketLeave(protocol, msg) + Market.onMarketLeave() + return true +end + +local function parseMarketDetail(protocol, msg) + local itemId = msg:getU16() + + local descriptions = {} + for i = MarketItemDescription.First, MarketItemDescription.Last do + if msg:peekU16() ~= 0x00 then + table.insert(descriptions, {i, msg:getString()}) -- item descriptions + else + msg:getU16() + end + end + + if g_game.getProtocolVersion() >= 1100 then -- imbuements + if msg:peekU16() ~= 0x00 then + table.insert(descriptions, {MarketItemDescription.Last + 1, msg:getString()}) + else + msg:getU16() + end + end + + local time = (os.time() / 1000) * statistics.SECONDS_PER_DAY; + + local purchaseStats = {} + local count = msg:getU8() + for i=1, count do + local transactions = msg:getU32() -- transaction count + local totalPrice = msg:getU32() -- total price + local highestPrice = msg:getU32() -- highest price + local lowestPrice = msg:getU32() -- lowest price + + local tmp = time - statistics.SECONDS_PER_DAY + table.insert(purchaseStats, OfferStatistic.new(tmp, MarketAction.Buy, transactions, totalPrice, highestPrice, lowestPrice)) + end + + local saleStats = {} + count = msg:getU8() + for i=1, count do + local transactions = msg:getU32() -- transaction count + local totalPrice = msg:getU32() -- total price + local highestPrice = msg:getU32() -- highest price + local lowestPrice = msg:getU32() -- lowest price + + local tmp = time - statistics.SECONDS_PER_DAY + table.insert(saleStats, OfferStatistic.new(tmp, MarketAction.Sell, transactions, totalPrice, highestPrice, lowestPrice)) + end + + signalcall(Market.onMarketDetail, itemId, descriptions, purchaseStats, saleStats) + return true +end + +local function parseMarketBrowse(protocol, msg) + local var = msg:getU16() + local offers = {} + + local buyOfferCount = msg:getU32() + for i = 1, buyOfferCount do + table.insert(offers, readMarketOffer(msg, MarketAction.Buy, var)) + end + + local sellOfferCount = msg:getU32() + for i = 1, sellOfferCount do + table.insert(offers, readMarketOffer(msg, MarketAction.Sell, var)) + end + + signalcall(Market.onMarketBrowse, offers, var) + return true +end + +-- public functions +function initProtocol() + connect(g_game, { onGameStart = MarketProtocol.registerProtocol, + onGameEnd = MarketProtocol.unregisterProtocol }) + + -- reloading module + if g_game.isOnline() then + MarketProtocol.registerProtocol() + end + + MarketProtocol.silent(false) +end + +function terminateProtocol() + disconnect(g_game, { onGameStart = MarketProtocol.registerProtocol, + onGameEnd = MarketProtocol.unregisterProtocol }) + + -- reloading module + MarketProtocol.unregisterProtocol() + MarketProtocol = nil +end + +function MarketProtocol.updateProtocol(_protocol) + protocol = _protocol +end + +function MarketProtocol.registerProtocol() + if g_game.getFeature(GamePlayerMarket) then + ProtocolGame.registerOpcode(GameServerOpcodes.GameServerMarketEnter, parseMarketEnter) + ProtocolGame.registerOpcode(GameServerOpcodes.GameServerMarketLeave, parseMarketLeave) + ProtocolGame.registerOpcode(GameServerOpcodes.GameServerMarketDetail, parseMarketDetail) + ProtocolGame.registerOpcode(GameServerOpcodes.GameServerMarketBrowse, parseMarketBrowse) + end + MarketProtocol.updateProtocol(g_game.getProtocolGame()) +end + +function MarketProtocol.unregisterProtocol() + if g_game.getFeature(GamePlayerMarket) then + ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerMarketEnter, parseMarketEnter) + ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerMarketLeave, parseMarketLeave) + ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerMarketDetail, parseMarketDetail) + ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerMarketBrowse, parseMarketBrowse) + end + MarketProtocol.updateProtocol(nil) +end + +function MarketProtocol.silent(mode) + silent = mode +end + +-- sending protocols + +function MarketProtocol.sendMarketLeave() + if g_game.getFeature(GamePlayerMarket) then + local msg = OutputMessage.create() + msg:addU8(ClientOpcodes.ClientMarketLeave) + send(msg) + else + g_logger.error('MarketProtocol.sendMarketLeave does not support the current protocol.') + end +end + +function MarketProtocol.sendMarketBrowse(browseId) + if g_game.getFeature(GamePlayerMarket) then + local msg = OutputMessage.create() + msg:addU8(ClientOpcodes.ClientMarketBrowse) + msg:addU16(browseId) + send(msg) + else + g_logger.error('MarketProtocol.sendMarketBrowse does not support the current protocol.') + end +end + +function MarketProtocol.sendMarketBrowseMyOffers() + MarketProtocol.sendMarketBrowse(MarketRequest.MyOffers) +end + +function MarketProtocol.sendMarketBrowseMyHistory() + MarketProtocol.sendMarketBrowse(MarketRequest.MyHistory) +end + +function MarketProtocol.sendMarketCreateOffer(type, spriteId, amount, price, anonymous) + if g_game.getFeature(GamePlayerMarket) then + local msg = OutputMessage.create() + msg:addU8(ClientOpcodes.ClientMarketCreate) + msg:addU8(type) + msg:addU16(spriteId) + msg:addU16(amount) + msg:addU32(price) + msg:addU8(anonymous) + send(msg) + else + g_logger.error('MarketProtocol.sendMarketCreateOffer does not support the current protocol.') + end +end + +function MarketProtocol.sendMarketCancelOffer(timestamp, counter) + if g_game.getFeature(GamePlayerMarket) then + local msg = OutputMessage.create() + msg:addU8(ClientOpcodes.ClientMarketCancel) + msg:addU32(timestamp) + msg:addU16(counter) + send(msg) + else + g_logger.error('MarketProtocol.sendMarketCancelOffer does not support the current protocol.') + end +end + +function MarketProtocol.sendMarketAcceptOffer(timestamp, counter, amount) + if g_game.getFeature(GamePlayerMarket) then + local msg = OutputMessage.create() + msg:addU8(ClientOpcodes.ClientMarketAccept) + msg:addU32(timestamp) + msg:addU16(counter) + msg:addU16(amount) + send(msg) + else + g_logger.error('MarketProtocol.sendMarketAcceptOffer does not support the current protocol.') + end +end diff --git a/800OTClient/modules/game_market/offerstatistic.lua b/800OTClient/modules/game_market/offerstatistic.lua new file mode 100644 index 0000000..859679b --- /dev/null +++ b/800OTClient/modules/game_market/offerstatistic.lua @@ -0,0 +1,101 @@ +OfferStatistic = {} +OfferStatistic.__index = OfferStatistic + +SECONDS_PER_DAY = 86400 + +OfferStatistic.new = function(timestamp, t, transactions, totalPrice, highestPrice, lowestPrice) + local stat = { + time = 0, + type = nil, + transactions = 0, + totalPrice = 0, + highestPrice = 0, + lowestPrice = 0 + } + stat.time = math.floor(timestamp / SECONDS_PER_DAY) * SECONDS_PER_DAY + + if t ~= MarketAction.Buy and t ~= MarketAction.Sell then + g_logger.error('OfferStatistic.new - invalid type provided.') + end + stat.type = t + + stat.transactions = transactions + stat.totalPrice = totalPrice + stat.highestPrice = highestPrice + stat.lowestPrice = lowestPrice + + setmetatable(stat, OfferStatistic) + return stat +end + +function OfferStatistic:isNull() + return self.time == 0 or not self.type +end + +-- Sets/Gets + +function OfferStatistic:setTime(time) + if not time or type(time) ~= 'number' then + g_logger.error('OfferStatistic.setTime - invalid time provided.') + end + self.time = time +end + +function OfferStatistic:getTime() + return self.time +end + +function OfferStatistic:setType(t) + if not t or type(t) ~= 'number' then + g_logger.error('OfferStatistic.setType - invalid type provided.') + end + self.type = t +end + +function OfferStatistic:getType() + return self.type +end + +function OfferStatistic:setTransactions(transactions) + if not transactions or type(transactions) ~= 'number' then + g_logger.error('OfferStatistic.setTransactions - invalid transactions provided.') + end + self.transactions = transactions +end + +function OfferStatistic:getTransactions() + return self.transactions +end + +function OfferStatistic:setTotalPrice(amount) + if not totalPrice or type(totalPrice) ~= 'number' then + g_logger.error('OfferStatistic.setTotalPrice - invalid total price provided.') + end + self.totalPrice = totalPrice +end + +function OfferStatistic:getTotalPrice() + return self.totalPrice +end + +function OfferStatistic:setHighestPrice(highestPrice) + if not highestPrice or type(highestPrice) ~= 'number' then + g_logger.error('OfferStatistic.setHighestPrice - invalid highestPrice provided.') + end + self.highestPrice = highestPrice +end + +function OfferStatistic:getHighestPrice() + return self.highestPrice +end + +function OfferStatistic:setLowestPrice(lowestPrice) + if not lowestPrice or type(lowestPrice) ~= 'number' then + g_logger.error('OfferStatistic.setLowestPrice - invalid lowestPrice provided.') + end + self.lowestPrice = lowestPrice +end + +function OfferStatistic:getLowestPrice() + return self.lowestPrice +end diff --git a/800OTClient/modules/game_market/ui/general/amountwindow.otui b/800OTClient/modules/game_market/ui/general/amountwindow.otui new file mode 100644 index 0000000..c4a2d25 --- /dev/null +++ b/800OTClient/modules/game_market/ui/general/amountwindow.otui @@ -0,0 +1,44 @@ +AmountWindow < MainWindow + id: amountWindow + !text: tr('Amount') + size: 270 90 + + Item + id: item + text-offset: 0 22 + text-align: right + anchors.left: parent.left + anchors.top: parent.top + margin-top: 2 + margin-left: -4 + focusable: false + virtual: true + + HorizontalScrollBar + id: amountScrollBar + anchors.left: prev.right + anchors.right: parent.right + anchors.top: prev.top + margin-left: 10 + margin-top: -2 + + Button + id: buttonCancel + !text: tr('Cancel') + height: 20 + anchors.left: amountScrollBar.horizontalCenter + anchors.right: amountScrollBar.right + anchors.top: amountScrollBar.bottom + margin-top: 7 + focusable: false + + Button + id: buttonOk + !text: tr('Ok') + height: 20 + anchors.right: amountScrollBar.horizontalCenter + anchors.left: amountScrollBar.left + anchors.top: amountScrollBar.bottom + margin-top: 7 + margin-right: 6 + focusable: false diff --git a/800OTClient/modules/game_market/ui/general/marketbuttons.otui b/800OTClient/modules/game_market/ui/general/marketbuttons.otui new file mode 100644 index 0000000..4533070 --- /dev/null +++ b/800OTClient/modules/game_market/ui/general/marketbuttons.otui @@ -0,0 +1,13 @@ +MarketButtonBox < ButtonBoxRounded + font: verdana-11px-rounded + color: #f55e5ebb + size: 106 22 + text-offset: 0 2 + text-align: center + + $checked: + color: white + + $disabled: + color: #666666ff + image-color: #ffffff88 diff --git a/800OTClient/modules/game_market/ui/general/marketcombobox.otui b/800OTClient/modules/game_market/ui/general/marketcombobox.otui new file mode 100644 index 0000000..8009483 --- /dev/null +++ b/800OTClient/modules/game_market/ui/general/marketcombobox.otui @@ -0,0 +1,18 @@ +MarketComboBoxPopupMenuButton < ComboBoxPopupMenuButton + height: 18 + font: verdana-11px-rounded + text-offset: 2 2 + +MarketComboBoxPopupMenuSeparator < UIWidget + image-source: /images/combobox_rounded + image-repeated: true + image-clip: 1 59 89 1 + height: 1 + phantom: true + +MarketComboBoxPopupMenu < ComboBoxPopupMenu + +MarketComboBox < ComboBox + font: verdana-11px-rounded + size: 86 20 + text-offset: 3 2 diff --git a/800OTClient/modules/game_market/ui/general/markettabs.otui b/800OTClient/modules/game_market/ui/general/markettabs.otui new file mode 100644 index 0000000..dbc6ca1 --- /dev/null +++ b/800OTClient/modules/game_market/ui/general/markettabs.otui @@ -0,0 +1,44 @@ +MarketTabBar < TabBar +MarketTabBarPanel < TabBarPanel +MarketTabBarButton < TabBarButton + size: 20 25 + font: verdana-11px-rounded + text-offset: 0 2 + + $!first: + anchors.left: prev.right + margin-left: 0 + + $hover !checked: + color: #ffffff + + $checked: + color: #ffffff + + $on !checked: + color: #f55e5e + +MarketRightTabBar < TabBar +MarketRightTabBarPanel < TabBarPanel +MarketRightTabBarButton < TabBarButton + size: 20 25 + font: verdana-11px-rounded + text-offset: 0 2 + color: #929292 + + $first: + anchors.right: parent.right + anchors.left: none + + $!first: + anchors.right: prev.left + anchors.left: none + + $hover !checked: + color: #ffffff + + $checked: + color: #ffffff + + $on !checked: + color: #f55e5e diff --git a/800OTClient/modules/game_market/ui/marketoffers.otui b/800OTClient/modules/game_market/ui/marketoffers.otui new file mode 100644 index 0000000..f8e04f8 --- /dev/null +++ b/800OTClient/modules/game_market/ui/marketoffers.otui @@ -0,0 +1,188 @@ +Panel + + MarketTabBar + id: leftTabBar + width: 107 + height:25 + anchors.top: parent.top + anchors.left: parent.left + + Panel + id: leftTabContent + width: 180 + anchors.top: prev.bottom + anchors.left: prev.left + anchors.bottom: parent.bottom + border-width: 1 + border-color: #000000 + + MarketRightTabBar + id: rightTabBar + width: 166 + height:25 + anchors.top: parent.top + anchors.right: parent.right + + Panel + id: rightTabContent + anchors.top: prev.bottom + anchors.left: leftTabContent.right + anchors.right: prev.right + anchors.bottom: parent.bottom + margin-left:3 + border-width: 1 + border-color: #000000 + + UIItem + id: selectedItem + phantom: true + size: 34 34 + padding: 1 + font: verdana-11px-rounded + border-color: white + anchors.top: rightTabBar.bottom + anchors.left: rightTabContent.left + margin-top: 6 + margin-left: 6 + + Label + id: nameLabel + !text: tr('No item selected.') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 5 + + Label + id: createLabel + !text: tr('Create New Offer') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: rightTabBar.top + anchors.left: rightTabContent.left + margin-top: 355 + margin-left: 6 + + Label + id: offerTypeLabel + !text: tr('Offer Type') .. ':' + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 7 + + MarketComboBox + id: offerTypeComboBox + !text: tr('Please Select') + anchors.top: prev.bottom + anchors.left: createLabel.left + margin-top: 3 + width: 105 + + $disabled: + color: #aaaaaa44 + + Label + id: totalPriceLabel + !text: tr('Total Price') .. ':' + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: offerTypeLabel.top + anchors.left: prev.right + margin-left: 7 + + SpinBox + id: totalPriceEdit + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 3 + width: 75 + minimum: 1 + maximum: 999999999 + focusable: true + + $disabled: + color: #aaaaaa44 + + Label + id: piecePriceLabel + !text: tr('Piece Price') .. ':' + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: offerTypeLabel.top + anchors.left: prev.right + margin-left: 7 + + SpinBox + id: piecePriceEdit + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 3 + width: 75 + minimum: 1 + maximum: 999999999 + focusable: true + + $disabled: + color: #aaaaaa44 + + Label + id: amountLabel + !text: tr('Amount') .. ':' + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: offerTypeLabel.top + anchors.left: amountEdit.left + + PreviousButton + id: prevAmountButton + anchors.verticalCenter: piecePriceEdit.verticalCenter + anchors.left: piecePriceEdit.right + margin-left: 7 + @onClick: Market.decrementAmount() + + SpinBox + id: amountEdit + anchors.top: prev.top + anchors.left: prev.right + margin-left: 3 + width: 55 + buttons: false + minimum: 1 + maximum: 64000 + focusable: true + + NextButton + id: nextAmountButton + anchors.verticalCenter: piecePriceEdit.verticalCenter + anchors.left: prev.right + margin-left: 3 + @onClick: Market.incrementAmount() + + Button + id: createOfferButton + !text: tr('Create Offer') + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 7 + width: 90 + + CheckBox + id: anonymousCheckBox + !text: tr('Anonymous') + anchors.left: prev.left + anchors.bottom: prev.top + margin-bottom: 6 + @onSetup: self:setChecked(false) + height: 16 + width: 90 + + Label + id: feeLabel + font: verdana-11px-rounded + anchors.top: createOfferButton.bottom + anchors.left: createOfferButton.left + margin: 2 \ No newline at end of file diff --git a/800OTClient/modules/game_market/ui/marketoffers/browse.otui b/800OTClient/modules/game_market/ui/marketoffers/browse.otui new file mode 100644 index 0000000..9071697 --- /dev/null +++ b/800OTClient/modules/game_market/ui/marketoffers/browse.otui @@ -0,0 +1,158 @@ +MarketItemBox < UICheckBox + id: itemBox + border-width: 1 + border-color: #000000 + color: #aaaaaa + text-align: center + + Item + id: item + phantom: true + virtual: true + text-offset: 0 22 + text-align: right + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin: 1 + + $checked: + border-color: #ffffff + + $hover !checked: + border-color: #aaaaaa + + $disabled: + image-color: #ffffff88 + color: #aaaaaa88 + +Panel + background-color: #22283399 + margin: 1 + + MarketComboBox + id: categoryComboBox + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + margin-top: 3 + margin-right: 3 + margin-left: 3 + + MarketComboBox + id: subCategoryComboBox + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 3 + margin-right: 3 + margin-left: 3 + + $disabled: + color: #aaaaaa44 + + MarketButtonBox + id: filterLevel + &default: false + !text: tr('Level') + !tooltip: tr('Filter list to match your level') + anchors.top: prev.bottom + anchors.left: parent.left + margin-top: 3 + margin-right: 3 + margin-left: 3 + width: 40 + height: 20 + + MarketButtonBox + id: filterVocation + &default: false + !text: tr('Voc.') + !tooltip: tr('Filter list to match your vocation') + anchors.top: prev.top + anchors.left: prev.right + margin-right: 3 + margin-left: 3 + width: 34 + height: 20 + + MarketComboBox + id: slotComboBox + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-right: 3 + margin-left: 3 + + $disabled: + color: #aaaaaa44 + + MarketButtonBox + id: filterDepot + &default: false + !text: tr('Show Depot Only') + !tooltip: tr('Show your depot items only') + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 6 + margin-right: 3 + margin-left: 3 + + Panel + id: itemsContainer + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + margin-top: 10 + margin-left: 3 + margin-bottom: 30 + margin-right: 3 + + VerticalScrollBar + id: itemsPanelListScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 28 + pixels-scroll: true + + ScrollablePanel + id: itemsPanel + anchors.left: parent.left + anchors.right: prev.left + anchors.top: parent.top + anchors.bottom: parent.bottom + vertical-scrollbar: itemsPanelListScrollBar + layout: + type: grid + cell-size: 36 36 + flow: true + auto-spacing: true + + Label + !text: tr('Find') .. ':' + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 9 + width: 30 + font: verdana-11px-rounded + text-offset: 0 2 + + TextEdit + id: searchEdit + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 3 + width: 113 + @onTextChange: Market.updateCurrentItems() + + MarketButtonBox + id: filterSearchAll + &default: true + !text: tr('All') + !tooltip: tr('Search all items') + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + anchors.right: itemsContainer.right + margin-left: 3 diff --git a/800OTClient/modules/game_market/ui/marketoffers/itemdetails.otui b/800OTClient/modules/game_market/ui/marketoffers/itemdetails.otui new file mode 100644 index 0000000..d90dbee --- /dev/null +++ b/800OTClient/modules/game_market/ui/marketoffers/itemdetails.otui @@ -0,0 +1,56 @@ +DetailsTableRow < TableRow + font: verdana-11px-monochrome + focusable: true + color: #cccccc + height: 45 + focusable: false + padding: 2 + even-background-color: alpha + odd-background-color: alpha + +DetailsTableColumn < TableColumn + font: verdana-11px-monochrome + background-color: alpha + text-offset: 2 2 + color: #cccccc + width: 100 + focusable: false + +Panel + background-color: #22283399 + margin: 1 + + Table + id: detailsTable + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 63 + margin-left: 6 + margin-bottom: 85 + margin-right: 6 + padding: 1 + focusable: false + background-color: #222833 + border-width: 1 + border-color: #191f27 + table-data: detailsTableData + row-style: DetailsTableRow + column-style: DetailsTableColumn + + TableData + id: detailsTableData + anchors.top: detailsTable.top + anchors.bottom: detailsTable.bottom + anchors.left: detailsTable.left + anchors.right: detailsTable.right + vertical-scrollbar: detailsTableScrollBar + + VerticalScrollBar + id: detailsTableScrollBar + anchors.top: detailsTable.top + anchors.bottom: detailsTable.bottom + anchors.right: detailsTable.right + step: 28 + pixels-scroll: true \ No newline at end of file diff --git a/800OTClient/modules/game_market/ui/marketoffers/itemoffers.otui b/800OTClient/modules/game_market/ui/marketoffers/itemoffers.otui new file mode 100644 index 0000000..8f39260 --- /dev/null +++ b/800OTClient/modules/game_market/ui/marketoffers/itemoffers.otui @@ -0,0 +1,176 @@ +OfferTableRow < TableRow + font: verdana-11px-monochrome + color: #cccccc + height: 15 + +OfferTableColumn < TableColumn + font: verdana-11px-monochrome + background-color: alpha + text-offset: 5 0 + color: #cccccc + width: 80 + +OfferTableWarningColumn < OfferTableColumn + color: #e03d3d + +OfferTableHeaderRow < TableHeaderRow + font: verdana-11px-monochrome + color: #cccccc + height: 20 + +OfferTableHeaderColumn < SortableTableHeaderColumn + font: verdana-11px-monochrome + text-offset: 2 0 + color: #cccccc + + $focus: + background-color: #294f6d + color: #ffffff + +Panel + background-color: #22283399 + margin: 1 + + Button + id: buyButton + !text: tr('Buy Now') + anchors.right: parent.right + anchors.bottom: next.bottom + margin-right: 6 + width: 80 + enabled: false + + Label + !text: tr('Sell Offers') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: parent.top + anchors.left: parent.left + margin-top: 44 + margin-left: 6 + + Table + id: sellingTable + anchors.top: prev.bottom + anchors.left: prev.left + anchors.right: parent.right + height: 115 + margin-top: 5 + margin-bottom: 5 + margin-right: 6 + padding: 1 + focusable: false + background-color: #222833 + border-width: 1 + border-color: #191f27 + table-data: sellingTableData + row-style: OfferTableRow + column-style: OfferTableColumn + header-column-style: false + header-row-style: false + + OfferTableHeaderRow + id: header + OfferTableHeaderColumn + !text: tr('Buyer Name') + width: 100 + OfferTableHeaderColumn + !text: tr('Amount') + width: 60 + OfferTableHeaderColumn + !text: tr('Total Price') + width: 90 + OfferTableHeaderColumn + !text: tr('Piece Price') + width: 80 + OfferTableHeaderColumn + !text: tr('Auction End') + width: 120 + + TableData + id: sellingTableData + anchors.bottom: sellingTable.bottom + anchors.left: sellingTable.left + anchors.right: sellingTable.right + margin-top: 2 + vertical-scrollbar: sellingTableScrollBar + + VerticalScrollBar + id: sellingTableScrollBar + anchors.top: sellingTable.top + anchors.bottom: sellingTable.bottom + anchors.right: sellingTable.right + step: 28 + pixels-scroll: true + + Button + id: sellButton + !text: tr('Sell Now') + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 5 + margin-right: 6 + width: 80 + enabled: false + + Label + !text: tr('Buy Offers') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: prev.top + anchors.left: parent.left + margin-top: 9 + margin-left: 6 + + Table + id: buyingTable + anchors.top: prev.bottom + anchors.left: prev.left + anchors.right: parent.right + margin-top: 5 + margin-bottom: 5 + margin-right: 6 + height: 115 + padding: 1 + focusable: false + background-color: #222833 + border-width: 1 + border-color: #191f27 + table-data: buyingTableData + row-style: OfferTableRow + column-style: OfferTableColumn + header-column-style: false + header-row-style: false + + OfferTableHeaderRow + id: header + OfferTableHeaderColumn + !text: tr('Seller Name') + width: 100 + OfferTableHeaderColumn + !text: tr('Amount') + width: 60 + OfferTableHeaderColumn + !text: tr('Total Price') + width: 90 + OfferTableHeaderColumn + !text: tr('Piece Price') + width: 80 + OfferTableHeaderColumn + !text: tr('Auction End') + width: 120 + + TableData + id: buyingTableData + anchors.bottom: buyingTable.bottom + anchors.left: buyingTable.left + anchors.right: buyingTable.right + vertical-scrollbar: buyingTableScrollBar + + VerticalScrollBar + id: buyingTableScrollBar + anchors.top: buyingTable.top + anchors.bottom: buyingTable.bottom + anchors.right: buyingTable.right + step: 28 + pixels-scroll: true \ No newline at end of file diff --git a/800OTClient/modules/game_market/ui/marketoffers/itemstats.otui b/800OTClient/modules/game_market/ui/marketoffers/itemstats.otui new file mode 100644 index 0000000..61afa97 --- /dev/null +++ b/800OTClient/modules/game_market/ui/marketoffers/itemstats.otui @@ -0,0 +1,103 @@ +StatsTableRow < TableRow + font: verdana-11px-monochrome + focusable: true + color: #cccccc + height: 20 + focusable: false + +StatsTableColumn < TableColumn + font: verdana-11px-monochrome + background-color: alpha + text-offset: 5 3 + color: #cccccc + width: 110 + focusable: false + +Panel + background-color: #22283399 + margin: 1 + + Label + !text: tr('Buy Offers') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + margin-top: 44 + margin-left: 6 + + Table + id: buyStatsTable + anchors.top: prev.bottom + anchors.left: prev.left + anchors.right: prev.right + margin-top: 6 + margin-bottom: 5 + margin-right: 6 + height: 121 + padding: 1 + focusable: false + background-color: #222833 + border-width: 1 + border-color: #191f27 + table-data: buyStatsTableData + row-style: StatsTableRow + column-style: StatsTableColumn + + TableData + id: buyStatsTableData + anchors.top: buyStatsTable.top + anchors.bottom: buyStatsTable.bottom + anchors.left: buyStatsTable.left + anchors.right: buyStatsTable.right + vertical-scrollbar: buyStatsTableScrollBar + + VerticalScrollBar + id: buyStatsTableScrollBar + anchors.top: buyStatsTable.top + anchors.bottom: buyStatsTable.bottom + anchors.right: buyStatsTable.right + step: 28 + pixels-scroll: true + + Label + !text: tr('Sell Offers') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: buyStatsTable.bottom + anchors.left: parent.left + margin-top: 9 + margin-left: 6 + + Table + id: sellStatsTable + anchors.top: prev.bottom + anchors.left: buyStatsTable.left + anchors.right: buyStatsTable.right + margin-top: 6 + height: 112 + padding: 1 + focusable: false + background-color: #222833 + border-width: 1 + border-color: #191f27 + table-data: sellStatsTableData + row-style: StatsTableRow + column-style: StatsTableColumn + + TableData + id: sellStatsTableData + anchors.top: sellStatsTable.top + anchors.bottom: sellStatsTable.bottom + anchors.left: sellStatsTable.left + anchors.right: sellStatsTable.right + vertical-scrollbar: sellStatsTableScrollBar + + VerticalScrollBar + id: sellStatsTableScrollBar + anchors.top: sellStatsTable.top + anchors.bottom: sellStatsTable.bottom + anchors.right: sellStatsTable.right + step: 28 + pixels-scroll: true \ No newline at end of file diff --git a/800OTClient/modules/game_market/ui/marketoffers/overview.otui b/800OTClient/modules/game_market/ui/marketoffers/overview.otui new file mode 100644 index 0000000..7e9cccb --- /dev/null +++ b/800OTClient/modules/game_market/ui/marketoffers/overview.otui @@ -0,0 +1,16 @@ +Panel + background-color: #22283399 + margin: 1 + + Label + !text: tr('Reserved for more functionality later.') + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + margin-top: 6 + margin-left: 6 + margin-right: 6 + font: verdana-11px-rounded + text-offset: 0 2 + height: 50 + text-wrap: true \ No newline at end of file diff --git a/800OTClient/modules/game_market/ui/myoffers.otui b/800OTClient/modules/game_market/ui/myoffers.otui new file mode 100644 index 0000000..cfa39d0 --- /dev/null +++ b/800OTClient/modules/game_market/ui/myoffers.otui @@ -0,0 +1,16 @@ +Panel + + MarketTabBar + id: offersTabBar + width: 187 + height:25 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Panel + id: offersTabContent + anchors.top: prev.bottom + anchors.left: prev.left + anchors.right: prev.right + anchors.bottom: parent.bottom diff --git a/800OTClient/modules/game_market/ui/myoffers/currentoffers.otui b/800OTClient/modules/game_market/ui/myoffers/currentoffers.otui new file mode 100644 index 0000000..6c53dac --- /dev/null +++ b/800OTClient/modules/game_market/ui/myoffers/currentoffers.otui @@ -0,0 +1,178 @@ +OfferTableRow < TableRow + font: verdana-11px-monochrome + color: #cccccc + height: 15 + +OfferTableColumn < TableColumn + font: verdana-11px-monochrome + background-color: alpha + text-offset: 5 0 + color: #cccccc + width: 80 + +OfferTableWarningColumn < OfferTableColumn + color: #e03d3d + +OfferTableHeaderRow < TableHeaderRow + font: verdana-11px-monochrome + color: #cccccc + height: 20 + +OfferTableHeaderColumn < SortableTableHeaderColumn + font: verdana-11px-monochrome + text-offset: 2 0 + color: #cccccc + + $focus: + background-color: #294f6d + color: #ffffff + +Panel + background-color: #22283399 + margin: 1 + + Button + id: sellCancelButton + !text: tr('Cancel') + anchors.right: parent.right + anchors.bottom: next.bottom + margin-right: 6 + width: 80 + enabled: false + + Label + !text: tr('Sell Offers') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: parent.top + anchors.left: parent.left + margin-top: 20 + margin-left: 6 + + Table + id: mySellingTable + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 160 + margin-top: 5 + margin-bottom: 5 + margin-left: 6 + margin-right: 6 + padding: 1 + focusable: false + background-color: #222833 + border-width: 1 + border-color: #191f27 + table-data: mySellingTableData + row-style: OfferTableRow + column-style: OfferTableColumn + header-column-style: false + header-row-style: false + + OfferTableHeaderRow + id: header + OfferTableHeaderColumn + !text: tr('Item Name') + width: 160 + OfferTableHeaderColumn + !text: tr('Total Price') + width: 125 + OfferTableHeaderColumn + !text: tr('Piece Price') + width: 125 + OfferTableHeaderColumn + !text: tr('Amount') + width: 100 + OfferTableHeaderColumn + !text: tr('Auction End') + width: 120 + + TableData + id: mySellingTableData + anchors.bottom: mySellingTable.bottom + anchors.left: mySellingTable.left + anchors.right: mySellingTable.right + margin-top: 2 + vertical-scrollbar: mySellingTableScrollBar + + VerticalScrollBar + id: mySellingTableScrollBar + anchors.top: mySellingTable.top + anchors.bottom: mySellingTable.bottom + anchors.right: mySellingTable.right + step: 28 + pixels-scroll: true + + Label + !text: tr('Buy Offers') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: prev.bottom + anchors.left: parent.left + margin-top: 20 + margin-left: 6 + + Button + id: buyCancelButton + !text: tr('Cancel') + anchors.right: parent.right + anchors.bottom: prev.bottom + margin-top: 5 + margin-right: 6 + width: 80 + enabled: false + + Table + id: myBuyingTable + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 5 + margin-bottom: 5 + margin-left: 6 + margin-right: 6 + height: 160 + padding: 1 + focusable: false + background-color: #222833 + border-width: 1 + border-color: #191f27 + table-data: myBuyingTableData + row-style: OfferTableRow + column-style: OfferTableColumn + header-column-style: false + header-row-style: false + + OfferTableHeaderRow + id: header + OfferTableHeaderColumn + !text: tr('Item Name') + width: 160 + OfferTableHeaderColumn + !text: tr('Total Price') + width: 125 + OfferTableHeaderColumn + !text: tr('Piece Price') + width: 125 + OfferTableHeaderColumn + !text: tr('Amount') + width: 100 + OfferTableHeaderColumn + !text: tr('Auction End') + width: 120 + + TableData + id: myBuyingTableData + anchors.bottom: myBuyingTable.bottom + anchors.left: myBuyingTable.left + anchors.right: myBuyingTable.right + vertical-scrollbar: myBuyingTableScrollBar + + VerticalScrollBar + id: myBuyingTableScrollBar + anchors.top: myBuyingTable.top + anchors.bottom: myBuyingTable.bottom + anchors.right: myBuyingTable.right + step: 28 + pixels-scroll: true diff --git a/800OTClient/modules/game_market/ui/myoffers/itemoffers.otui b/800OTClient/modules/game_market/ui/myoffers/itemoffers.otui new file mode 100644 index 0000000..c649ce8 --- /dev/null +++ b/800OTClient/modules/game_market/ui/myoffers/itemoffers.otui @@ -0,0 +1,9 @@ +Panel + background-color: #22283399 + margin: 1 + + Label + !text: tr('Item Offers') + anchors.top: parent.top + anchors.left: parent.left + margin-left: 10 diff --git a/800OTClient/modules/game_market/ui/myoffers/offerhistory.otui b/800OTClient/modules/game_market/ui/myoffers/offerhistory.otui new file mode 100644 index 0000000..3f8380d --- /dev/null +++ b/800OTClient/modules/game_market/ui/myoffers/offerhistory.otui @@ -0,0 +1,61 @@ +Panel + background-color: #22283399 + margin: 1 + + Table + id: myHistoryTable + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + height: 390 + margin-top: 5 + margin-bottom: 5 + margin-left: 8 + margin-right: 8 + padding: 1 + focusable: false + background-color: #222833 + border-width: 1 + border-color: #191f27 + table-data: myHistoryTableData + row-style: OfferTableRow + column-style: OfferTableColumn + header-column-style: false + header-row-style: false + + OfferTableHeaderRow + id: header + OfferTableHeaderColumn + !text: tr('Action') + width: 60 + OfferTableHeaderColumn + !text: tr('Item Name') + width: 140 + OfferTableHeaderColumn + !text: tr('Total Price') + width: 115 + OfferTableHeaderColumn + !text: tr('Piece Price') + width: 115 + OfferTableHeaderColumn + !text: tr('Amount') + width: 75 + OfferTableHeaderColumn + !text: tr('Auction End') + width: 120 + + TableData + id: myHistoryTableData + anchors.bottom: myHistoryTable.bottom + anchors.left: myHistoryTable.left + anchors.right: myHistoryTable.right + margin-top: 2 + vertical-scrollbar: myHistoryTableScrollBar + + VerticalScrollBar + id: myHistoryTableScrollBar + anchors.top: myHistoryTable.top + anchors.bottom: myHistoryTable.bottom + anchors.right: myHistoryTable.right + step: 28 + pixels-scroll: true \ No newline at end of file diff --git a/800OTClient/modules/game_minimap/flagwindow.otui b/800OTClient/modules/game_minimap/flagwindow.otui new file mode 100644 index 0000000..c0fac4a --- /dev/null +++ b/800OTClient/modules/game_minimap/flagwindow.otui @@ -0,0 +1,188 @@ +FlagButton < CheckBox + size: 15 15 + margin-left: 2 + image-source: /images/game/minimap/flagcheckbox + image-size: 15 15 + image-border: 3 + icon-source: /images/game/minimap/mapflags + icon-size: 11 11 + icon-clip: 0 0 11 11 + icon-offset: 2 4 + text: + + $!checked: + image-clip: 26 0 26 26 + + $hover !checked: + image-clip: 78 0 26 26 + + $checked: + image-clip: 0 0 26 26 + + $hover checked: + image-clip: 52 0 26 26 + + +FlagWindow < MainWindow + id: flagWindow + !text: tr('Create Map Mark') + size: 196 185 + + Label + id: position + !text: tr('Position') .. ':' + text-auto-resize: true + anchors.top: parent.top + anchors.left: parent.left + margin-top: 2 + + Label + !text: tr('Description') .. ':' + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 7 + + TextEdit + id: description + margin-top: 3 + anchors.left: parent.left + anchors.top: prev.bottom + width: 158 + + FlagButton + id: flag1 + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 6 + margin-left: 0 + + FlagButton + id: flag2 + icon-clip: 11 0 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag3 + icon-clip: 22 0 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag4 + icon-clip: 33 0 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag5 + icon-clip: 44 0 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag6 + icon-clip: 55 0 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag7 + icon-clip: 66 0 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag8 + icon-clip: 77 0 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag9 + icon-clip: 88 0 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag10 + icon-clip: 99 0 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag11 + icon-clip: 0 11 11 11 + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 6 + margin-left: 0 + + FlagButton + id: flag12 + icon-clip: 11 11 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag13 + icon-clip: 22 11 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag14 + icon-clip: 33 11 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag15 + icon-clip: 44 11 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag16 + icon-clip: 55 11 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag17 + icon-clip: 66 11 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag18 + icon-clip: 77 11 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag19 + icon-clip: 88 11 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag20 + icon-clip: 99 11 11 11 + anchors.left: prev.right + anchors.top: prev.top + + Button + id: okButton + !text: tr('Ok') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + + Button + id: cancelButton + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom diff --git a/800OTClient/modules/game_minimap/minimap.lua b/800OTClient/modules/game_minimap/minimap.lua new file mode 100644 index 0000000..9c1835a --- /dev/null +++ b/800OTClient/modules/game_minimap/minimap.lua @@ -0,0 +1,164 @@ +minimapWidget = nil +minimapButton = nil +minimapWindow = nil +fullmapView = false +loaded = false +oldZoom = nil +oldPos = nil + +function init() + minimapWindow = g_ui.loadUI('minimap', modules.game_interface.getRightPanel()) + minimapWindow:setContentMinimumHeight(64) + + if not minimapWindow.forceOpen then + minimapButton = modules.client_topmenu.addRightGameToggleButton('minimapButton', + tr('Minimap') .. ' (Ctrl+M)', '/images/topbuttons/minimap', toggle) + minimapButton:setOn(true) + end + + minimapWidget = minimapWindow:recursiveGetChildById('minimap') + + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.bindKeyPress('Alt+Left', function() minimapWidget:move(1,0) end, gameRootPanel) + g_keyboard.bindKeyPress('Alt+Right', function() minimapWidget:move(-1,0) end, gameRootPanel) + g_keyboard.bindKeyPress('Alt+Up', function() minimapWidget:move(0,1) end, gameRootPanel) + g_keyboard.bindKeyPress('Alt+Down', function() minimapWidget:move(0,-1) end, gameRootPanel) + g_keyboard.bindKeyDown('Ctrl+M', toggle) + g_keyboard.bindKeyDown('Ctrl+Shift+M', toggleFullMap) + + minimapWindow:setup() + + connect(g_game, { + onGameStart = online, + onGameEnd = offline, + }) + + connect(LocalPlayer, { + onPositionChange = updateCameraPosition + }) + + if g_game.isOnline() then + online() + end +end + +function terminate() + if g_game.isOnline() then + saveMap() + end + + disconnect(g_game, { + onGameStart = online, + onGameEnd = offline, + }) + + disconnect(LocalPlayer, { + onPositionChange = updateCameraPosition + }) + + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.unbindKeyPress('Alt+Left', gameRootPanel) + g_keyboard.unbindKeyPress('Alt+Right', gameRootPanel) + g_keyboard.unbindKeyPress('Alt+Up', gameRootPanel) + g_keyboard.unbindKeyPress('Alt+Down', gameRootPanel) + g_keyboard.unbindKeyDown('Ctrl+M') + g_keyboard.unbindKeyDown('Ctrl+Shift+M') + + minimapWindow:destroy() + if minimapButton then + minimapButton:destroy() + end +end + +function toggle() + if not minimapButton then return end + if minimapButton:isOn() then + minimapWindow:close() + minimapButton:setOn(false) + else + minimapWindow:open() + minimapButton:setOn(true) + end +end + +function onMiniWindowClose() + if minimapButton then + minimapButton:setOn(false) + end +end + +function online() + loadMap() + updateCameraPosition() +end + +function offline() + saveMap() +end + +function loadMap() + local clientVersion = g_game.getClientVersion() + + g_minimap.clean() + loaded = false + + local minimapFile = '/minimap.otmm' + local dataMinimapFile = '/data' .. minimapFile + local versionedMinimapFile = '/minimap' .. clientVersion .. '.otmm' + if g_resources.fileExists(dataMinimapFile) then + loaded = g_minimap.loadOtmm(dataMinimapFile) + end + if not loaded and g_resources.fileExists(versionedMinimapFile) then + loaded = g_minimap.loadOtmm(versionedMinimapFile) + end + if not loaded and g_resources.fileExists(minimapFile) then + loaded = g_minimap.loadOtmm(minimapFile) + end + if not loaded then + print("Minimap couldn't be loaded, file missing?") + end + minimapWidget:load() +end + +function saveMap() + local clientVersion = g_game.getClientVersion() + local minimapFile = '/minimap' .. clientVersion .. '.otmm' + g_minimap.saveOtmm(minimapFile) + minimapWidget:save() +end + +function updateCameraPosition() + local player = g_game.getLocalPlayer() + if not player then return end + local pos = player:getPosition() + if not pos then return end + if not minimapWidget:isDragging() then + if not fullmapView then + minimapWidget:setCameraPosition(player:getPosition()) + end + minimapWidget:setCrossPosition(player:getPosition()) + end +end + +function toggleFullMap() + if not fullmapView then + fullmapView = true + minimapWindow:hide() + minimapWidget:setParent(modules.game_interface.getRootPanel()) + minimapWidget:fill('parent') + minimapWidget:setAlternativeWidgetsVisible(true) + else + fullmapView = false + minimapWidget:setParent(minimapWindow:getChildById('contentsPanel')) + minimapWidget:fill('parent') + minimapWindow:show() + minimapWidget:setAlternativeWidgetsVisible(false) + end + + local zoom = oldZoom or 0 + local pos = oldPos or minimapWidget:getCameraPosition() + oldZoom = minimapWidget:getZoom() + oldPos = minimapWidget:getCameraPosition() + minimapWidget:setZoom(zoom) + minimapWidget:setCameraPosition(pos) +end diff --git a/800OTClient/modules/game_minimap/minimap.otmod b/800OTClient/modules/game_minimap/minimap.otmod new file mode 100644 index 0000000..a62ba81 --- /dev/null +++ b/800OTClient/modules/game_minimap/minimap.otmod @@ -0,0 +1,9 @@ +Module + name: game_minimap + description: Manage minimap + author: edubart, BeniS + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ minimap ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_minimap/minimap.otui b/800OTClient/modules/game_minimap/minimap.otui new file mode 100644 index 0000000..61e358c --- /dev/null +++ b/800OTClient/modules/game_minimap/minimap.otui @@ -0,0 +1,7 @@ +MinimapWindow + id: minimapWindow + !text: tr('Minimap') + icon: /images/topbuttons/minimap + @onClose: modules.game_minimap.onMiniWindowClose() + &save: true + &autoOpen: 1 diff --git a/800OTClient/modules/game_modaldialog/modaldialog.lua b/800OTClient/modules/game_modaldialog/modaldialog.lua new file mode 100644 index 0000000..c842553 --- /dev/null +++ b/800OTClient/modules/game_modaldialog/modaldialog.lua @@ -0,0 +1,136 @@ +modalDialog = nil +lastDialogChoices = 0 +lastDialogChoice = 0 +lastDialogAnswer = 0 + +function init() + g_ui.importStyle('modaldialog') + + connect(g_game, { onModalDialog = onModalDialog, + onGameEnd = destroyDialog }) + + local dialog = rootWidget:recursiveGetChildById('modalDialog') + if dialog then + modalDialog = dialog + end +end + +function terminate() + disconnect(g_game, { onModalDialog = onModalDialog, + onGameEnd = destroyDialog }) +end + +function destroyDialog() + if modalDialog then + modalDialog:destroy() + modalDialog = nil + end +end + +function onModalDialog(id, title, message, buttons, enterButton, escapeButton, choices, priority) + -- priority parameter is unused, not sure what its use is. + if modalDialog then + return + end + + modalDialog = g_ui.createWidget('ModalDialog', rootWidget) + + local messageLabel = modalDialog:getChildById('messageLabel') + local choiceList = modalDialog:getChildById('choiceList') + local choiceScrollbar = modalDialog:getChildById('choiceScrollBar') + local buttonsPanel = modalDialog:getChildById('buttonsPanel') + + modalDialog:setText(title) + messageLabel:setText(message) + + local labelHeight + for i = 1, #choices do + local choiceId = choices[i][1] + local choiceName = choices[i][2] + + local label = g_ui.createWidget('ChoiceListLabel', choiceList) + label.choiceId = choiceId + label:setText(choiceName) + label:setPhantom(false) + if not labelHeight then + labelHeight = label:getHeight() + end + end + if #choices > 0 then + if g_clock.millis() < lastDialogAnswer + 1000 and lastDialogChoices == #choices then + choiceList:focusChild(choiceList:getChildByIndex(lastDialogChoice)) + else + choiceList:focusChild(choiceList:getFirstChild()) + end + end + + local buttonsWidth = 0 + for i = 1, #buttons do + local buttonId = buttons[i][1] + local buttonText = buttons[i][2] + + local button = g_ui.createWidget('ModalButton', buttonsPanel) + button:setText(buttonText) + button.onClick = function(self) + local focusedChoice = choiceList:getFocusedChild() + local choice = 0xFF + if focusedChoice then + choice = focusedChoice.choiceId + lastDialogChoice = choiceList:getChildIndex(focusedChoice) + lastDialogAnswer = g_clock.millis() + end + g_game.answerModalDialog(id, buttonId, choice) + destroyDialog() + end + buttonsWidth = buttonsWidth + button:getWidth() + button:getMarginLeft() + button:getMarginRight() + end + + local additionalHeight = 0 + if #choices > 0 then + choiceList:setVisible(true) + choiceScrollbar:setVisible(true) + + additionalHeight = math.min(modalDialog.maximumChoices, math.max(modalDialog.minimumChoices, #choices)) * labelHeight + additionalHeight = additionalHeight + choiceList:getPaddingTop() + choiceList:getPaddingBottom() + end + + local horizontalPadding = modalDialog:getPaddingLeft() + modalDialog:getPaddingRight() + buttonsWidth = buttonsWidth + horizontalPadding + + local labelWidth = math.min(600, math.floor(message:len() * 1.5)) + modalDialog:setWidth(math.min(modalDialog.maximumWidth, math.max(buttonsWidth, labelWidth, modalDialog.minimumWidth))) + messageLabel:setTextWrap(true) + + modalDialog:setHeight(90 + additionalHeight + messageLabel:getHeight()) + + local enterFunc = function() + local focusedChoice = choiceList:getFocusedChild() + local choice = 0xFF + if focusedChoice then + choice = focusedChoice.choiceId + lastDialogChoice = choiceList:getChildIndex(focusedChoice) + lastDialogAnswer = g_clock.millis() + end + g_game.answerModalDialog(id, enterButton, choice) + destroyDialog() + end + + local escapeFunc = function() + local focusedChoice = choiceList:getFocusedChild() + local choice = 0xFF + if focusedChoice then + choice = focusedChoice.choiceId + lastDialogChoice = choiceList:getChildIndex(focusedChoice) + lastDialogAnswer = g_clock.millis() + end + g_game.answerModalDialog(id, escapeButton, choice) + destroyDialog() + end + + choiceList.onDoubleClick = enterFunc + + modalDialog.onEnter = enterFunc + modalDialog.onEscape = escapeFunc + + lastDialogChoices = #choices +end \ No newline at end of file diff --git a/800OTClient/modules/game_modaldialog/modaldialog.otmod b/800OTClient/modules/game_modaldialog/modaldialog.otmod new file mode 100644 index 0000000..237e067 --- /dev/null +++ b/800OTClient/modules/game_modaldialog/modaldialog.otmod @@ -0,0 +1,10 @@ +Module + name: game_modaldialog + description: Show and process modal dialogs + author: Summ + website: https://github.com/edubart/otclient + sandboxed: true + dependencies: [ game_interface ] + scripts: [ modaldialog ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_modaldialog/modaldialog.otui b/800OTClient/modules/game_modaldialog/modaldialog.otui new file mode 100644 index 0000000..2d67bea --- /dev/null +++ b/800OTClient/modules/game_modaldialog/modaldialog.otui @@ -0,0 +1,74 @@ +ChoiceListLabel < Label + font: verdana-11px-monochrome + background-color: alpha + text-offset: 2 0 + focusable: true + + $focus: + background-color: #00000055 + color: #ffffff + +ChoiceList < TextList + id: choiceList + vertical-scrollbar: choiceScrollBar + anchors.fill: parent + anchors.top: prev.bottom + anchors.bottom: next.top + margin-top: 4 + margin-bottom: 10 + focusable: false + visible: false + +ChoiceScrollBar < VerticalScrollBar + id: choiceScrollBar + anchors.top: choiceList.top + anchors.bottom: choiceList.bottom + anchors.right: choiceList.right + step: 14 + pixels-scroll: true + visible: false + +ModalButton < Button + text-auto-resize: true + margin-top: 2 + margin-bottom: 2 + margin-left: 2 + + $pressed: + text-offset: 0 0 + +ModalDialog < MainWindow + id: modalDialog + size: 280 97 + &minimumWidth: 300 + &maximumWidth: 600 + &minimumChoices: 4 + &maximumChoices: 10 + + Label + id: messageLabel + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-align: left + text-auto-resize: true + text-wrap: true + + ChoiceList + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + margin-bottom: 5 + + Panel + id: buttonsPanel + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: 24 + layout: horizontalBox + align-right: true + + ChoiceScrollBar diff --git a/800OTClient/modules/game_npctrade/npctrade.lua b/800OTClient/modules/game_npctrade/npctrade.lua new file mode 100644 index 0000000..d271ffe --- /dev/null +++ b/800OTClient/modules/game_npctrade/npctrade.lua @@ -0,0 +1,588 @@ +BUY = 1 +SELL = 2 +CURRENCY = 'gold' +CURRENCY_DECIMAL = false +WEIGHT_UNIT = 'oz' +LAST_INVENTORY = 10 + +npcWindow = nil +itemsPanel = nil +radioTabs = nil +radioItems = nil +searchText = nil +setupPanel = nil +quantity = nil +quantityScroll = nil +idLabel = nil +nameLabel = nil +priceLabel = nil +moneyLabel = nil +weightDesc = nil +weightLabel = nil +capacityDesc = nil +capacityLabel = nil +tradeButton = nil +buyTab = nil +sellTab = nil +initialized = false + +showWeight = true +buyWithBackpack = nil +ignoreCapacity = nil +ignoreEquipped = nil +showAllItems = nil +sellAllButton = nil +sellAllWithDelayButton = nil +playerFreeCapacity = 0 +playerMoney = 0 +tradeItems = {} +playerItems = {} +selectedItem = nil + +cancelNextRelease = nil + +sellAllWithDelayEvent = nil + +function init() + npcWindow = g_ui.displayUI('npctrade') + npcWindow:setVisible(false) + + itemsPanel = npcWindow:recursiveGetChildById('itemsPanel') + searchText = npcWindow:recursiveGetChildById('searchText') + + setupPanel = npcWindow:recursiveGetChildById('setupPanel') + quantityScroll = setupPanel:getChildById('quantityScroll') + idLabel = setupPanel:getChildById('id') + nameLabel = setupPanel:getChildById('name') + priceLabel = setupPanel:getChildById('price') + moneyLabel = setupPanel:getChildById('money') + weightDesc = setupPanel:getChildById('weightDesc') + weightLabel = setupPanel:getChildById('weight') + capacityDesc = setupPanel:getChildById('capacityDesc') + capacityLabel = setupPanel:getChildById('capacity') + tradeButton = npcWindow:recursiveGetChildById('tradeButton') + + buyWithBackpack = npcWindow:recursiveGetChildById('buyWithBackpack') + ignoreCapacity = npcWindow:recursiveGetChildById('ignoreCapacity') + ignoreEquipped = npcWindow:recursiveGetChildById('ignoreEquipped') + showAllItems = npcWindow:recursiveGetChildById('showAllItems') + sellAllButton = npcWindow:recursiveGetChildById('sellAllButton') + sellAllWithDelayButton = npcWindow:recursiveGetChildById('sellAllWithDelayButton') + buyTab = npcWindow:getChildById('buyTab') + sellTab = npcWindow:getChildById('sellTab') + + radioTabs = UIRadioGroup.create() + radioTabs:addWidget(buyTab) + radioTabs:addWidget(sellTab) + radioTabs:selectWidget(buyTab) + radioTabs.onSelectionChange = onTradeTypeChange + + cancelNextRelease = false + + if g_game.isOnline() then + playerFreeCapacity = g_game.getLocalPlayer():getFreeCapacity() + end + + connect(g_game, { onGameEnd = hide, + onOpenNpcTrade = onOpenNpcTrade, + onCloseNpcTrade = onCloseNpcTrade, + onPlayerGoods = onPlayerGoods } ) + + connect(LocalPlayer, { onFreeCapacityChange = onFreeCapacityChange, + onInventoryChange = onInventoryChange } ) + + initialized = true +end + +function terminate() + initialized = false + npcWindow:destroy() + removeEvent(sellAllWithDelayEvent) + + disconnect(g_game, { onGameEnd = hide, + onOpenNpcTrade = onOpenNpcTrade, + onCloseNpcTrade = onCloseNpcTrade, + onPlayerGoods = onPlayerGoods } ) + + disconnect(LocalPlayer, { onFreeCapacityChange = onFreeCapacityChange, + onInventoryChange = onInventoryChange } ) +end + +function show() + if g_game.isOnline() then + if #tradeItems[BUY] > 0 then + radioTabs:selectWidget(buyTab) + else + radioTabs:selectWidget(sellTab) + end + + npcWindow:show() + npcWindow:raise() + npcWindow:focus() + end +end + +function hide() + removeEvent(sellAllWithDelayEvent) + + npcWindow:hide() + + local layout = itemsPanel:getLayout() + layout:disableUpdates() + + clearSelectedItem() + + searchText:clearText() + setupPanel:disable() + itemsPanel:destroyChildren() + + if radioItems then + radioItems:destroy() + radioItems = nil + end + + layout:enableUpdates() + layout:update() +end + +function onItemBoxChecked(widget) + if widget:isChecked() then + local item = widget.item + selectedItem = item + refreshItem(item) + tradeButton:enable() + + if getCurrentTradeType() == SELL then + quantityScroll:setValue(quantityScroll:getMaximum()) + end + end +end + +function onQuantityValueChange(quantity) + if selectedItem then + weightLabel:setText(string.format('%.2f', selectedItem.weight*quantity) .. ' ' .. WEIGHT_UNIT) + priceLabel:setText(formatCurrency(getItemPrice(selectedItem))) + end +end + +function onTradeTypeChange(radioTabs, selected, deselected) + tradeButton:setText(selected:getText()) + selected:setOn(true) + deselected:setOn(false) + + local currentTradeType = getCurrentTradeType() + buyWithBackpack:setVisible(currentTradeType == BUY) + ignoreCapacity:setVisible(currentTradeType == BUY) + ignoreEquipped:setVisible(currentTradeType == SELL) + showAllItems:setVisible(currentTradeType == SELL) + sellAllButton:setVisible(currentTradeType == SELL) + sellAllWithDelayButton:setVisible(currentTradeType == SELL) + + refreshTradeItems() + refreshPlayerGoods() +end + +function onTradeClick() + removeEvent(sellAllWithDelayEvent) + if getCurrentTradeType() == BUY then + g_game.buyItem(selectedItem.ptr, quantityScroll:getValue(), ignoreCapacity:isChecked(), buyWithBackpack:isChecked()) + else + g_game.sellItem(selectedItem.ptr, quantityScroll:getValue(), ignoreEquipped:isChecked()) + end +end + +function onSearchTextChange() + refreshPlayerGoods() +end + +function itemPopup(self, mousePosition, mouseButton) + if cancelNextRelease then + cancelNextRelease = false + return false + end + + if mouseButton == MouseRightButton then + local menu = g_ui.createWidget('PopupMenu') + menu:setGameMenu(true) + menu:addOption(tr('Look'), function() return g_game.inspectNpcTrade(self:getItem()) end) + menu:display(mousePosition) + return true + elseif ((g_mouse.isPressed(MouseLeftButton) and mouseButton == MouseRightButton) + or (g_mouse.isPressed(MouseRightButton) and mouseButton == MouseLeftButton)) then + cancelNextRelease = true + g_game.inspectNpcTrade(self:getItem()) + return true + end + return false +end + +function onBuyWithBackpackChange() + if selectedItem then + refreshItem(selectedItem) + end +end + +function onIgnoreCapacityChange() + refreshPlayerGoods() +end + +function onIgnoreEquippedChange() + refreshPlayerGoods() +end + +function onShowAllItemsChange() + refreshPlayerGoods() +end + +function setCurrency(currency, decimal) + CURRENCY = currency + CURRENCY_DECIMAL = decimal +end + +function setShowWeight(state) + showWeight = state + weightDesc:setVisible(state) + weightLabel:setVisible(state) +end + +function setShowYourCapacity(state) + capacityDesc:setVisible(state) + capacityLabel:setVisible(state) + ignoreCapacity:setVisible(state) +end + +function clearSelectedItem() + idLabel:clearText() + nameLabel:clearText() + weightLabel:clearText() + priceLabel:clearText() + tradeButton:disable() + quantityScroll:setMinimum(0) + quantityScroll:setMaximum(0) + if selectedItem then + radioItems:selectWidget(nil) + selectedItem = nil + end +end + +function getCurrentTradeType() + if tradeButton:getText() == tr('Buy') then + return BUY + else + return SELL + end +end + +function getItemPrice(item, single) + local amount = 1 + local single = single or false + if not single then + amount = quantityScroll:getValue() + end + if getCurrentTradeType() == BUY then + if buyWithBackpack:isChecked() then + if item.ptr:isStackable() then + return item.price*amount + 20 + else + return item.price*amount + math.ceil(amount/20)*20 + end + end + end + return item.price*amount +end + +function getSellQuantity(item) + if not item or not playerItems[item:getId()] then return 0 end + local removeAmount = 0 + if ignoreEquipped:isChecked() then + local localPlayer = g_game.getLocalPlayer() + for i=1,LAST_INVENTORY do + local inventoryItem = localPlayer:getInventoryItem(i) + if inventoryItem and inventoryItem:getId() == item:getId() then + removeAmount = removeAmount + inventoryItem:getCount() + end + end + end + return playerItems[item:getId()] - removeAmount +end + +function canTradeItem(item) + if getCurrentTradeType() == BUY then + return (ignoreCapacity:isChecked() or (not ignoreCapacity:isChecked() and playerFreeCapacity >= item.weight)) and playerMoney >= getItemPrice(item, true) + else + return getSellQuantity(item.ptr) > 0 + end +end + +function refreshItem(item) + idLabel:setText(item.ptr:getId()) + nameLabel:setText(item.name) + weightLabel:setText(string.format('%.2f', item.weight) .. ' ' .. WEIGHT_UNIT) + priceLabel:setText(formatCurrency(getItemPrice(item))) + + if getCurrentTradeType() == BUY then + local capacityMaxCount = math.floor(playerFreeCapacity / item.weight) + if ignoreCapacity:isChecked() then + capacityMaxCount = 65535 + end + local priceMaxCount = math.floor(playerMoney / getItemPrice(item, true)) + local finalCount = math.max(0, math.min(getMaxAmount(), math.min(priceMaxCount, capacityMaxCount))) + quantityScroll:setMinimum(1) + quantityScroll:setMaximum(finalCount) + else + quantityScroll:setMinimum(1) + quantityScroll:setMaximum(math.max(0, math.min(getMaxAmount(), getSellQuantity(item.ptr)))) + end + + setupPanel:enable() +end + +function refreshTradeItems() + local layout = itemsPanel:getLayout() + layout:disableUpdates() + + clearSelectedItem() + + searchText:clearText() + setupPanel:disable() + itemsPanel:destroyChildren() + + if radioItems then + radioItems:destroy() + end + radioItems = UIRadioGroup.create() + + local currentTradeItems = tradeItems[getCurrentTradeType()] + for key,item in pairs(currentTradeItems) do + local itemBox = g_ui.createWidget('NPCItemBox', itemsPanel) + itemBox.item = item + + local text = '' + local name = item.name + text = text .. name + if showWeight then + local weight = string.format('%.2f', item.weight) .. ' ' .. WEIGHT_UNIT + text = text .. '\n' .. weight + end + local price = formatCurrency(item.price) + text = text .. '\n' .. price + itemBox:setText(text) + + local itemWidget = itemBox:getChildById('item') + itemWidget:setItem(item.ptr) + itemWidget.onMouseRelease = itemPopup + + radioItems:addWidget(itemBox) + end + + layout:enableUpdates() + layout:update() +end + +function refreshPlayerGoods() + if not initialized then return end + + checkSellAllTooltip() + + moneyLabel:setText(formatCurrency(playerMoney)) + capacityLabel:setText(string.format('%.2f', playerFreeCapacity) .. ' ' .. WEIGHT_UNIT) + + local currentTradeType = getCurrentTradeType() + local searchFilter = searchText:getText():lower() + local foundSelectedItem = false + + local items = itemsPanel:getChildCount() + for i=1,items do + local itemWidget = itemsPanel:getChildByIndex(i) + local item = itemWidget.item + + local canTrade = canTradeItem(item) + itemWidget:setOn(canTrade) + itemWidget:setEnabled(canTrade) + + local searchCondition = (searchFilter == '') or (searchFilter ~= '' and string.find(item.name:lower(), searchFilter) ~= nil) + local showAllItemsCondition = (currentTradeType == BUY) or (showAllItems:isChecked()) or (currentTradeType == SELL and not showAllItems:isChecked() and canTrade) + itemWidget:setVisible(searchCondition and showAllItemsCondition) + + if selectedItem == item and itemWidget:isEnabled() and itemWidget:isVisible() then + foundSelectedItem = true + end + end + + if not foundSelectedItem then + clearSelectedItem() + end + + if selectedItem then + refreshItem(selectedItem) + end +end + +function onOpenNpcTrade(items) + tradeItems[BUY] = {} + tradeItems[SELL] = {} + for key,item in pairs(items) do + if item[4] > 0 then + local newItem = {} + newItem.ptr = item[1] + newItem.name = item[2] + newItem.weight = item[3] / 100 + newItem.price = item[4] + table.insert(tradeItems[BUY], newItem) + end + + if item[5] > 0 then + local newItem = {} + newItem.ptr = item[1] + newItem.name = item[2] + newItem.weight = item[3] / 100 + newItem.price = item[5] + table.insert(tradeItems[SELL], newItem) + end + end + + refreshTradeItems() + addEvent(show) -- player goods has not been parsed yet +end + +function closeNpcTrade() + g_game.closeNpcTrade() + addEvent(hide) +end + +function onCloseNpcTrade() + addEvent(hide) +end + +function onPlayerGoods(money, items) + playerMoney = money + + playerItems = {} + for key,item in pairs(items) do + local id = item[1]:getId() + if not playerItems[id] then + playerItems[id] = item[2] + else + playerItems[id] = playerItems[id] + item[2] + end + end + + refreshPlayerGoods() +end + +function onFreeCapacityChange(localPlayer, freeCapacity, oldFreeCapacity) + playerFreeCapacity = freeCapacity + + if npcWindow:isVisible() then + refreshPlayerGoods() + end +end + +function onInventoryChange(inventory, item, oldItem) + refreshPlayerGoods() +end + +function getTradeItemData(id, type) + if table.empty(tradeItems[type]) then + return false + end + + if type then + for key,item in pairs(tradeItems[type]) do + if item.ptr and item.ptr:getId() == id then + return item + end + end + else + for _,items in pairs(tradeItems) do + for key,item in pairs(items) do + if item.ptr and item.ptr:getId() == id then + return item + end + end + end + end + return false +end + +function checkSellAllTooltip() + sellAllButton:setEnabled(true) + sellAllButton:removeTooltip() + sellAllWithDelayButton:setEnabled(true) + sellAllWithDelayButton:removeTooltip() + + local total = 0 + local info = '' + local first = true + + for key, amount in pairs(playerItems) do + local data = getTradeItemData(key, SELL) + if data then + amount = getSellQuantity(data.ptr) + if amount > 0 then + if data and amount > 0 then + info = info..(not first and "\n" or "").. + amount.." ".. + data.name.." (".. + data.price*amount.." gold)" + + total = total+(data.price*amount) + if first then first = false end + end + end + end + end + if info ~= '' then + info = info.."\nTotal: "..total.." gold" + sellAllButton:setTooltip(info) + sellAllWithDelayButton:setTooltip(info) + else + sellAllButton:setEnabled(false) + sellAllWithDelayButton:setEnabled(false) + end +end + +function formatCurrency(amount) + if CURRENCY_DECIMAL then + return string.format("%.02f", amount/100.0) .. ' ' .. CURRENCY + else + return amount .. ' ' .. CURRENCY + end +end + +function getMaxAmount() + if getCurrentTradeType() == SELL and g_game.getFeature(GameDoubleShopSellAmount) then + return 10000 + end + return 100 +end + +function sellAll(delayed, exceptions) + -- backward support + if type(delayed) == "table" then + exceptions = delayed + delayed = false + end + exceptions = exceptions or {} + removeEvent(sellAllWithDelayEvent) + local queue = {} + for _,entry in ipairs(tradeItems[SELL]) do + local id = entry.ptr:getId() + if not table.find(exceptions, id) then + local sellQuantity = getSellQuantity(entry.ptr) + while sellQuantity > 0 do + local maxAmount = math.min(sellQuantity, getMaxAmount()) + if delayed then + g_game.sellItem(entry.ptr, maxAmount, ignoreEquipped:isChecked()) + sellAllWithDelayEvent = scheduleEvent(function() sellAll(true) end, 1100) + return + end + table.insert(queue, {entry.ptr, maxAmount, ignoreEquipped:isChecked()}) + sellQuantity = sellQuantity - maxAmount + end + end + end + for _, entry in ipairs(queue) do + g_game.sellItem(entry[1], entry[2], entry[3]) + end +end diff --git a/800OTClient/modules/game_npctrade/npctrade.otmod b/800OTClient/modules/game_npctrade/npctrade.otmod new file mode 100644 index 0000000..df1e957 --- /dev/null +++ b/800OTClient/modules/game_npctrade/npctrade.otmod @@ -0,0 +1,9 @@ +Module + name: game_npctrade + description: NPC trade interface + author: andrefaramir, baxnie + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ npctrade ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_npctrade/npctrade.otui b/800OTClient/modules/game_npctrade/npctrade.otui new file mode 100644 index 0000000..7278351 --- /dev/null +++ b/800OTClient/modules/game_npctrade/npctrade.otui @@ -0,0 +1,295 @@ +NPCOfferLabel < Label + anchors.left: prev.right + anchors.top: prev.top + margin-left: 5 + text-auto-resize: true + +NPCItemBox < UICheckBox + border-width: 1 + border-color: #000000 + color: #aaaaaa + text-align: center + text-offset: 0 30 + @onCheckChange: modules.game_npctrade.onItemBoxChecked(self) + + Item + id: item + phantom: true + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + image-color: #ffffffff + margin-top: 3 + + $checked on: + border-color: #ffffff + + $!checked: + border-color: #000000 + + $!on: + image-color: #ffffff88 + color: #aaaaaa88 + +MainWindow + id: npcWindow + !text: tr('NPC Trade') + size: 550 460 + @onEscape: modules.game_npctrade.closeNpcTrade() + + $mobile: + size: 550 360 + + TabButton + id: buyTab + !tooltip: tr("List of items that you're able to buy") + !text: tr('Buy') + checked: true + on: true + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: parent.top + margin-top: 0 + + TabButton + id: sellTab + !tooltip: tr("List of items that you're able to sell") + !text: tr('Sell') + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: parent.top + + FlatPanel + height: 250 + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 5 + + $mobile: + height: 150 + + VerticalScrollBar + id: itemsPanelListScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 24 + pixels-scroll: true + + ScrollablePanel + id: itemsPanel + height: 200 + anchors.left: parent.left + anchors.right: prev.left + anchors.top: parent.top + anchors.bottom: parent.bottom + vertical-scrollbar: itemsPanelListScrollBar + margin-left: 5 + margin-right: 5 + layout: + type: grid + cell-size: 160 90 + flow: true + auto-spacing: true + + FlatPanel + id: setupPanel + height: 105 + enabled: false + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-top: 5 + margin-right: 5 + image-color: #ffffff88 + + Label + !text: tr('Name') .. ':' + anchors.left: parent.left + anchors.top: parent.top + margin-top: 5 + margin-left: 5 + width: 85 + + NPCOfferLabel + id: name + + Label + !text: tr('Id') .. ':' + anchors.left: parent.left + anchors.top: parent.top + margin-top: 5 + margin-left: 5 + margin-left: 195 + width: 15 + + NPCOfferLabel + id: id + + Label + !text: tr('Price') .. ':' + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 3 + margin-left: 5 + width: 85 + + NPCOfferLabel + id: price + + Label + !text: tr('Your Money') .. ':' + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 3 + margin-left: 5 + width: 85 + + NPCOfferLabel + id: money + + Label + id: weightDesc + !text: tr('Weight') .. ':' + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 3 + margin-left: 5 + width: 85 + + NPCOfferLabel + id: weight + + Label + id: capacityDesc + !text: tr('Your Capacity') .. ':' + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 3 + margin-left: 5 + width: 85 + + NPCOfferLabel + id: capacity + + HorizontalScrollBar + id: quantityScroll + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 3 + margin-left: 5 + margin-right: 5 + show-value: true + minimum: 1 + maximum: 100 + step: 1 + @onValueChange: modules.game_npctrade.onQuantityValueChange(self:getValue()) + + FlatPanel + id: buyOptions + height: 80 + anchors.top: prev.top + anchors.left: parent.horizontalCenter + anchors.right: parent.right + margin-left: 5 + image-color: #ffffff88 + + Label + id: searchLabel + !text: tr('Search') .. ':' + anchors.left: parent.left + anchors.top: parent.top + text-auto-resize: true + margin-top: 7 + margin-left: 5 + + TextEdit + id: searchText + anchors.left: prev.right + anchors.top: prev.top + anchors.right: parent.right + margin-top: -2 + margin-left: 5 + margin-right: 5 + @onTextChange: modules.game_npctrade.onSearchTextChange() + + CheckBox + id: buyWithBackpack + !text: tr('Buy with backpack') + anchors.top: searchText.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-left: 5 + margin-top: 3 + @onCheckChange: modules.game_npctrade.onBuyWithBackpackChange() + + CheckBox + id: ignoreCapacity + !text: tr('Ignore capacity') + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-left: 5 + margin-top: 3 + @onCheckChange: modules.game_npctrade.onIgnoreCapacityChange() + + CheckBox + id: ignoreEquipped + !text: tr('Ignore equipped') + anchors.top: searchText.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-left: 5 + margin-top: 3 + visible: false + checked: true + @onCheckChange: modules.game_npctrade.onIgnoreEquippedChange() + + CheckBox + id: showAllItems + !text: tr('Show all items') + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-left: 5 + margin-top: 3 + visible: false + checked: true + @onCheckChange: modules.game_npctrade.onShowAllItemsChange() + + Button + id: sellAllWithDelayButton + !text: tr('Sell all with delay') + width: 128 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + visible: false + @onClick: modules.game_npctrade.sellAll(true) + + Button + id: sellAllButton + !text: tr('Sell all') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + visible: false + @onClick: modules.game_npctrade.sellAll() + + Button + id: tradeButton + !text: tr('Buy') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + @onClick: modules.game_npctrade.onTradeClick() + + Button + !text: tr('Close') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: modules.game_npctrade.closeNpcTrade() diff --git a/800OTClient/modules/game_outfit/outfit.lua b/800OTClient/modules/game_outfit/outfit.lua new file mode 100644 index 0000000..32d2ce5 --- /dev/null +++ b/800OTClient/modules/game_outfit/outfit.lua @@ -0,0 +1,960 @@ +local loadLocalShaders = false + +appearanceOptions = {} +configOptions = {} +previewOptions = {} +previewDir = 2 +filterText = "" +currentCategory = "outfit" +outfitWindow = nil +outfitCreatureBox = nil +currentColorBox = nil +currentClotheButtonBox = nil +colorBoxes = {} +dataTables = { + outfits = {}, + mounts = {}, + auras = {}, + wings = {}, + shaders = {}, + manaBar = {}, + healthBar = {} +} + +math.randomseed(os.time()) + +-- take local shaders, won't work if server does not support it +localShaders = {} +local shaderFiles = g_resources.listDirectoryFiles("/data/shaders/", true, false) +for i, file in ipairs(shaderFiles) do + local name = file:split(".")[1]:trim():lower() + name = name:gsub("/data/shaders//", "") + name = name:gsub("_fragment", "") + name = name:gsub("_vertex", "") + if name:find("outfit") and not table.find(localShaders, name) then + table.insert(localShaders, name) + end +end + +function setupTables() + configOptions = { + {id = "addon1", text = "Addon 1", checked = false, enabled = g_game.getClientVersion() >= 780}, + {id = "addon2", text = "Addon 2", checked = false, enabled = g_game.getClientVersion() >= 780}, + {id = "mount", text = "Mount", checked = false, enabled = g_game.getFeature(GamePlayerMounts)}, + {id = "wings", text = "Wings", checked = false, enabled = g_game.getFeature(GameWingsAndAura)}, + {id = "aura", text = "Aura", checked = false, enabled = g_game.getFeature(GameWingsAndAura)}, + {id = "shader", text = "Shaders", checked = false, enabled = g_game.getFeature(GameOutfitShaders) or loadLocalShaders and #localShaders > 0}, + {id = "healtbar", text = "Health Bars", checked = false, enabled = g_game.getFeature(GameHealthInfoBackground)}, + {id = "manabar", text = "Mana Bars", checked = false, enabled = g_game.getFeature(GameHealthInfoBackground)} + } + appearanceOptions = { + {id = "presetCat", text = "Preset", enabled = true}, + {id = "outfitCat", text = "Outfit", enabled = true}, + {id = "mountCat", text = "Mount", enabled = g_game.getFeature(GamePlayerMounts)}, + {id = "wingsCat", text = "Wings", enabled = g_game.getFeature(GameWingsAndAura)}, + {id = "auraCat", text = "Aura", enabled = g_game.getFeature(GameWingsAndAura)}, + {id = "shaderCat", text = "Shader", enabled = g_game.getFeature(GameOutfitShaders) or loadLocalShaders and #localShaders > 0}, + {id = "healtbarCat", text = "Health Bars", enabled = g_game.getFeature(GameHealthInfoBackground)}, + {id = "manabarCat", text = "Mana Bars", enabled = g_game.getFeature(GameHealthInfoBackground)} + } + previewOptions = { + {id = "move", text = "Movement", checked = false, enabled = true}, + {id = "showOutfit", text = "Outfit", checked = true, enabled = true}, + {id = "showMount", text = "Mount", checked = false, enabled = g_game.getFeature(GamePlayerMounts)}, + {id = "showWings", text = "Wings", checked = false, enabled = g_game.getFeature(GameWingsAndAura)}, + {id = "showAura", text = "Aura", checked = false, enabled = g_game.getFeature(GameWingsAndAura)}, + {id = "showShader", text = "Shader", checked = false, enabled = g_game.getFeature(GameOutfitShaders) or loadLocalShaders and #localShaders > 0} + } +end + +function init() + connect( + g_game, + { + onOpenOutfitWindow = create, + onGameEnd = destroy + } + ) +end + +function terminate() + disconnect( + g_game, + { + onOpenOutfitWindow = create, + onGameEnd = destroy + } + ) + destroy() +end + +function onFilterList(text) + if not outfitWindow then + return + end + filterText = text:lower() + + refreshVisiblePreviews() +end + +function clearFilterText() + if not outfitWindow then + return + end + + outfitWindow.search.filterWindow:setText("") +end + +function onPresetButtonPress(key) + local widget + + for i, child in ipairs(outfitWindow.list:getChildren()) do + if child.catalog == "preset" then + if child:isChecked() then + widget = child + break + end + end + end + + if key == "delete" then + if widget then + widget:destroy() + end + elseif key == "new" then + local outfit = getOutfitFromCurrentChecks(1) + outfit.mount = 0 + local mount = getOutfitFromCurrentChecks().mount + local name = "new preset" + + local widget = g_ui.createWidget("LargePreviewTile", outfitWindow.list) + widget.catalog = "preset" + widget:setId("preset." .. outfit.type .. name) + widget.outfit:setOutfit(outfit) + if mount then + widget.mount:setOutfit( + { + type = mount + } + ) + end + widget.title:setText(name) + elseif key == "rename" then + if widget then + modules.client_textedit.show(widget.title, {title = "Rename Preset", placeholder = widget.title:getText()}) + end + elseif key == "save" then + if widget then + local data = getOutfitFromCurrentChecks() + local outfit = data.outfit + local mount = data.mount + + widget.outfit:setOutfit(outfit) + if mount then + widget.mount:setOutfit(mount) + end + save() + end + end +end + +function onOptionChange(key, checked, widget) + if not outfitWindow then + return + end + local creature = outfitWindow.preview.creaturePanel.creature + + if key:find("show") or key:find("addon") then + refreshPreview() + end + + if key:find("Cat") then + currentCategory = string.sub(key, 1, key:len() - 3) + + -- set filter window title + outfitWindow.search.title:setText("Filter " .. currentCategory .. "s") + + if key == "presetCat" then + outfitWindow.list:getLayout():setNumColumns(1) + outfitWindow.list:getLayout():setCellSize({height = 100, width = 217}) + outfitWindow.search:setVisible(false) + outfitWindow.preset:setVisible(true) + else + outfitWindow.list:getLayout():setNumColumns(2) + outfitWindow.list:getLayout():setCellSize({height = 100, width = 106}) + outfitWindow.search:setVisible(true) + outfitWindow.preset:setVisible(false) + end + + -- set correct checks + for i, child in ipairs(widget:getParent():getParent():getChildren()) do + child.checkBox:setChecked(widget == child.checkBox) + end + + refreshVisiblePreviews() + elseif key == "move" then + creature:setAnimate(checked) + elseif key == "showOutfit" or key == "showMount" then + local options = outfitWindow.preview.options + local showOutfit = options.showOutfit + local showMount = options.showMount + showOutfit = showOutfit and showOutfit.check:isChecked() + showMount = showMount and showMount.check:isChecked() + + if not showMount and not showOutfit then + options.move.check:setChecked(false) + creature:setAnimate(false) + options.move:disable() + else + options.move:enable() + end + end +end + +function refreshVisiblePreviews() + if not outfitWindow then + return + end + + for i, child in ipairs(outfitWindow.list:getChildren()) do + local id = child:getId() + local catalog = string.split(id, ".")[1] + local name = string.split(id, ".")[2] + local show = catalog == currentCategory and name:find(filterText) + child:setVisible(show) + end +end + +function getOutfitFromCurrentChecks(returnVal) + returnVal = returnVal or 0 + + -- 0 - return raw table + -- 1 - return combined outfit according to configure checks + -- 2 - return combined outfit according to preview checks + if not outfitWindow then + return + end + + local data = { + cleanOutfit = {}, -- outfit.type & colors + mount = 0, -- outfit.mount + addons = 0, -- outfit.addons + shader = "", -- outfit.shader + wings = 0, -- outfit.wings + aura = 0, -- outfit.aura + healthbar = "", -- outfit.healthbar + manabar = "" -- outfit.manabar + } + + local combinedOutfit + local previewOutfit + local options = outfitWindow.config.options + local addon1 = options.addon1 + local addon2 = options.addon2 + addon1 = addon1 and addon1.check:isChecked() + addon2 = addon2 and addon2.check:isChecked() + local showAddons = addon1 and addon2 and 3 or addon2 and 2 or addon1 and 1 or 0 + local showMount = g_game.getFeature(GamePlayerMounts) and options.mount and options.mount.check:isChecked() + local showShader = (g_game.getFeature(GameOutfitShaders) or #localShaders > 0) and options.shader and options.shader.check:isChecked() + local showHealthBar = g_game.getFeature(GameHealthInfoBackground) and options.healthbar and options.healthbar:isChecked() + local showManaBar = g_game.getFeature(GameHealthInfoBackground) and options.manabar and options.manabar:isChecked() + local showAura = g_game.getFeature(GameWingsAndAura) and options.aura and options.aura:isChecked() + local showWings = g_game.getFeature(GameWingsAndAura) and options.wings and options.wings:isChecked() + + for i, child in ipairs(outfitWindow.list:getChildren()) do + if child:isChecked() and child.catalog ~= "preset" then + local catalog = child.catalog + local outfit = child.creature:getOutfit() + if catalog == "outfit" then -- get type and colors + data.cleanOutfit = outfit + elseif catalog == "mount" then + data[catalog] = outfit.type + elseif catalog == "shader" then + data[catalog] = child.shader + elseif catalog == "wings" then + data[catalog] = outfit.type + elseif catalog == "aura" then + data[catalog] = outfit.aura + elseif catalog == "healthbar" then + local id = string.split(child:getId(), " ")[2] + data[catalog] = id + elseif catalog == "manabar" then + local id = string.split(child:getId(), " ")[2] + data[catalog] = id + end + end + end + data.addons = showAddons + + if returnVal == 1 then + combinedOutfit = data.cleanOutfit + combinedOutfit.addons = showAddons + combinedOutfit.mount = showMount and data.mount > 0 and data.mount or nil + combinedOutfit.shader = showShader and data.shader:len() > 0 and data.shader or nil + combinedOutfit.wings = showWings and data.wings > 0 and data.wings or nil + combinedOutfit.aura = showAura and data.aura > 0 and data.aura or nil + combinedOutfit.healthbar = showHealthBar and data.healthbar:len() > 0 and data.healthbar or nil + combinedOutfit.manabar = showManaBar and data.manabar:len() > 0 and data.manabar or nil + elseif returnVal == 2 then + previewOutfit = data.cleanOutfit + previewOutfit.addons = showAddons + previewOutfit.mount = data.mount > 0 and data.mount or nil + previewOutfit.shader = data.shader:len() > 0 and data.shader or nil + previewOutfit.wings = data.wings > 0 and data.wings or nil + previewOutfit.aura = data.aura > 0 and data.aura or nil + previewOutfit.healthbar = data.healthbar:len() > 0 and data.healthbar or nil + previewOutfit.manabar = data.manabar:len() > 0 and data.manabar or nil + end + + -- TODO: test & most likely fix all custom features (wings, auras, shaders, bars) + if returnVal == 0 then + return data -- raw + elseif returnVal == 1 then + return combinedOutfit -- combined @ configure + else + return previewOutfit -- combined @ preview + end +end + +function randomize() + local outfitTemplate = { + outfitWindow.appearance.parts.head, + outfitWindow.appearance.parts.primary, + outfitWindow.appearance.parts.secondary, + outfitWindow.appearance.parts.detail + } + + for i = 1, #outfitTemplate do + local n = math.random(#colorBoxes) + + outfitTemplate[i]:setChecked(true) + colorBoxes[n]:setChecked(true) + outfitTemplate[i]:setChecked(false) + end + outfitTemplate[1]:setChecked(true) +end + +function onElementSelect(widget) + if not outfitWindow then + return + end + local catalog = string.split(widget:getId(), ".")[1] + + -- apply correct check + for i, child in ipairs(widget:getParent():getChildren()) do + -- there can be few items checked, but only one per catalog + if child.catalog == widget.catalog then + child:setChecked(widget == child) + end + end + + if catalog == "outfit" then + local outfit = widget.creature:getOutfit() + local addons = outfit.addons + + local addon1 = outfitWindow.config.options.addon1.check + local addon2 = outfitWindow.config.options.addon2.check + + addon1:setChecked(addons == 1 or addons == 3) + addon2:setChecked(addons > 1) + + addon1:setEnabled(addons == 1 or addons == 3) + addon2:setEnabled(addons > 1) + + refreshPreview() + setCategoryDescription(catalog, outfit.type) + elseif catalog == "mount" then + local outfit = widget.creature:getOutfit() + + refreshPreview() + setCategoryDescription(catalog, outfit.type) + elseif catalog == "preset" then + local outfit = widget.outfit:getOutfit().type + local mount = widget.mount:getOutfit().type + + for i, child in ipairs(outfitWindow.list:getChildren()) do + if child.catalog == "outfit" then + if child.creature:getOutfit().type == outfit then + onElementSelect(child) + end + end + if child.catalog == "mount" then + if child.creature:getOutfit().type == mount then + onElementSelect(child) + end + end + end + + setCategoryDescription(catalog, widget.title:getText()) + refreshPreview() + elseif catalog == "shader" then + local shader = widget.creature:getOutfit().shader + + setCategoryDescription(catalog, widget.title:getText()) + refreshPreview() + elseif catalog == "healthbar" then + elseif catalog == "manabar" then + elseif catalog == "wings" then + end +end + +function refreshPreview() + if not outfitWindow then + return + end + local creature = outfitWindow.preview.creaturePanel.creature + local options = outfitWindow.preview.options + + local outfit = getOutfitFromCurrentChecks(2) + + local showOutfit = options.showOutfit and options.showOutfit.check:isChecked() + local showMount = g_game.getFeature(GamePlayerMounts) and options.showMount and options.showMount.check:isChecked() + local showShader = (g_game.getFeature(GameOutfitShaders) or #localShaders > 0) and options.showShader and options.showShader.check:isChecked() + local showWings = g_game.getFeature(GameWingsAndAura) and options.showWings and options.showWings.check:isChecked() + local showAura = g_game.getFeature(GameWingsAndAura) and options.showAura and options.showAura.check:isChecked() + + if showOutfit then + outfit.mount = not showMount and 0 or outfit.mount + -- those things can only be displaed when showOutfit + outfit.shader = not showShader and "" or outfit.shader + outfit.wings = not showWings and 0 or outfit.wings + outfit.aura = not showAura and 0 or outfit.aura + elseif showMount then + outfit = {type = outfit.mount} + else + return creature:setOutfit({}) + end + + creature:setOutfit(outfit) +end + +function rotatePreview(side) + if not outfitWindow then + return + end + local creature = outfitWindow.preview.creaturePanel.creature + previewDir = side == "rotateLeft" and (previewDir + 1) or (previewDir - 1) + previewDir = previewDir % 4 + + creature:setDirection(previewDir) +end + +function setCategoryDescription(id, key) + if not outfitWindow then + return + end + + -- id can be widgetId so extract id + local type = string.split(id, ".")[1] -- ie. outfit + local tableKey = type .. "s" -- ie. outfits + local newId = type .. "Cat" -- ie. outfitCat + local table = dataTables[tableKey] + local widget = outfitWindow.appearance.categories[newId] + + widget = widget and widget.description + + if id == "preset" or id == "shader" then + return widget:setText(key) + end + + -- something went wrong + if not table or not widget then + return + end + + for i, data in ipairs(table) do + if data[1] == key then + return widget:setText(data[2]) + end + end + + widget:setText("-") +end + +function onClotheCheckChange(clotheButtonBox) + if not outfitWindow then + return + end + local outfit = outfitWindow.preview.creaturePanel.creature:getOutfit() + if clotheButtonBox == currentClotheButtonBox then + clotheButtonBox.onCheckChange = nil + clotheButtonBox:setChecked(true) + clotheButtonBox.onCheckChange = onClotheCheckChange + else + currentClotheButtonBox.onCheckChange = nil + currentClotheButtonBox:setChecked(false) + currentClotheButtonBox.onCheckChange = onClotheCheckChange + + currentClotheButtonBox = clotheButtonBox + + local colorId = 0 + if currentClotheButtonBox:getId() == "head" then + colorId = outfit.head + elseif currentClotheButtonBox:getId() == "primary" then + colorId = outfit.body + elseif currentClotheButtonBox:getId() == "secondary" then + colorId = outfit.legs + elseif currentClotheButtonBox:getId() == "detail" then + colorId = outfit.feet + end + outfitWindow.appearance.colorBoxPanel["colorBox" .. colorId]:setChecked(true) + end +end + +function onColorCheckChange(colorBox) + if not outfitWindow then + return + end + local outfit = outfitWindow.preview.creaturePanel.creature:getOutfit() + if colorBox == currentColorBox then + colorBox.onCheckChange = nil + colorBox:setChecked(true) + colorBox.onCheckChange = onColorCheckChange + else + if currentColorBox then + currentColorBox.onCheckChange = nil + currentColorBox:setChecked(false) + currentColorBox.onCheckChange = onColorCheckChange + end + + currentColorBox = colorBox + + if currentClotheButtonBox:getId() == "head" then + outfit.head = currentColorBox.colorId + elseif currentClotheButtonBox:getId() == "primary" then + outfit.body = currentColorBox.colorId + elseif currentClotheButtonBox:getId() == "secondary" then + outfit.legs = currentColorBox.colorId + elseif currentClotheButtonBox:getId() == "detail" then + outfit.feet = currentColorBox.colorId + end + outfitWindow.preview.creaturePanel.creature:setOutfit(outfit) + updateOutfits() + refreshPreview() + end +end + +function updateOutfits() + if not outfitWindow then + return + end + local outfit = outfitWindow.preview.creaturePanel.creature:getOutfit() + + for i, child in ipairs(outfitWindow.list:getChildren()) do + if child.catalog == "outfit" then + local previewOutfit = child.creature:getOutfit() + previewOutfit.head = outfit.head + previewOutfit.body = outfit.body + previewOutfit.legs = outfit.legs + previewOutfit.feet = outfit.feet + + child.creature:setOutfit(previewOutfit) + end + end +end + +function create(currentOutfit, outfitList, mountList, wingList, auraList, shaderList, hpBarList, manaBarList) + if outfitWindow and not outfitWindow:isHidden() then + return + end + + load() + destroy() + setupTables() + + -- use local shaders if server doesnt send any + shaderList = #shaderList > 0 and shaderList or loadLocalShaders and localShaders or {} + + outfitWindow = g_ui.displayUI("outfitwindow") + dataTables = { + outfits = outfitList, + mounts = mountList, + wings = wingList, + auras = auraList, + shaders = shaderList, + hpBars = hpBarList, + manaBars = manaBarList + } + + outfitWindow.appearance.onGeometryChange = function(widget, old, new) + local filterHeight = outfitWindow.search:getHeight() -- to detect layout used, 56 for default 47 for retro + local diff = 239 + filterHeight + local height = new.height + outfitWindow:setHeight(height + diff) + end + + local creature = outfitWindow.preview.creaturePanel.creature + local outfitType = currentOutfit.type + local mountType = currentOutfit.mount + local clearOutfit = currentOutfit + local currentAddons = currentOutfit.addons + + local availableAddons + for i, outfit in ipairs(outfitList) do + if outfit[1] == outfitType then + availableAddons = outfit[3] + end + end + + clearOutfit.mount = 0 + creature:setOutfit(clearOutfit) + + previewDir = 2 + + -- outfits + for i, outfit in ipairs(outfitList) do + local id = outfit[1] + local name = outfit[2] + local addons = outfit[3] + local outfit = currentOutfit + outfit.type = id + outfit.addons = addons + + local widget = g_ui.createWidget("SmallPreviewTile", outfitWindow.list) + widget:setId("outfit." .. name:lower() .. " " .. id) + widget.title:setText(name) + outfit.mount = 0 + widget.creature:setOutfit(outfit) + widget.catalog = "outfit" + end + + -- mounts + for i, mount in ipairs(mountList) do + local id = mount[1] + local name = mount[2] + local mountOufit = { + type = id + } + + local widget = g_ui.createWidget("SmallPreviewTile", outfitWindow.list) + widget:setId("mount." .. name:lower() .. " " .. id) + widget.title:setText(name) + widget.creature:setOutfit(mountOufit) + widget.catalog = "mount" + end + + -- wings + for i, wings in ipairs(wingList) do + local id = wings[1] + local name = wings[2] + local wingsOufit = { + type = id + } + + local widget = g_ui.createWidget("SmallPreviewTile", outfitWindow.list) + widget:setId("wings." .. name:lower() .. " " .. id) + widget.title:setText(name) + widget.creature:setOutfit(wingsOufit) + widget.catalog = "wings" + end + + -- auras + for i, aura in ipairs(auraList) do + local id = aura[1] + local name = aura[2] + local auraOufit = { + type = id + } + + local widget = g_ui.createWidget("SmallPreviewTile", outfitWindow.list) + widget:setId("aura." .. name:lower() .. " " .. id) + widget.title:setText(name) + widget.creature:setOutfit(auraOufit) + widget.catalog = "aura" + end + + -- shaders + for i, shader in ipairs(shaderList) do + if type(shader) ~= "table" then + shader = {i, shader} + end + local id = shader[1] + local name = shader[2] + local shaderOutfit = currentOutfit + shaderOutfit.shader = name + shaderOutfit.type = outfitType + + local widget = g_ui.createWidget("SmallPreviewTile", outfitWindow.list) + widget:setId("shader." .. name:lower() .. " " .. id) + widget.title:setText(name) + widget.creature:setOutfit(shaderOutfit) + widget.catalog = "shader" + widget.shader = shaderOutfit.shader + end + + if g_game.getFeature(GameHealthInfoBackground) then + table.insert(hpBarList, 1, {0, "-"}) + table.insert(manaBarList, 1, {0, "-"}) + end + + -- hpbar + for i, bar in ipairs(hpBarList) do + local id = bar[1] + local name = bar[2] + local path = g_healthBars.getHealthBarPath(id) + + local widget = g_ui.createWidget("SmallPreviewTile", outfitWindow.list) + widget:setId("healthbar." .. name:lower() .. " " .. id) + widget.item:setImageSource(i > 1 and path or "") + widget.title:setText(i > i and name or "Standard") + widget.catalog = "healthbar" + end + + -- hpbar + for i, bar in ipairs(manaBarList) do + local id = bar[1] + local name = bar[2] + local path = g_healthBars.getHealthBarPath(id) + + local widget = g_ui.createWidget("SmallPreviewTile", outfitWindow.list) + widget:setId("manabar." .. name:lower() .. " " .. id) + widget.item:setImageSource(i > 1 and path or "") + widget.title:setText(i > i and name or "Standard") + widget.catalog = "manabar" + end + + -- check current outfit + for i, child in ipairs(outfitWindow.list:getChildren()) do + local catalog = child.catalog + local outfit = child.creature:getOutfit() + + if catalog == "outfit" then + if outfit.type == outfitType then + child:setChecked(true) + outfitWindow.list:moveChildToIndex(child, 1) + end + elseif catalog == "mount" then + if outfit.type == mountType then + child:setChecked(true) + outfitWindow.list:moveChildToIndex(child, 1) + child:setVisible(false) + end + elseif catalog == "shader" then + if outfit.shader == currentOutfit.shader then + child:setChecked(true) + outfitWindow.list:moveChildToIndex(child, 1) + child:setVisible(false) + end + elseif catalog == "wings" then + if outfit.wings == currentOutfit.wings then + child:setChecked(true) + outfitWindow.list:moveChildToIndex(child, 1) + child:setVisible(false) + end + elseif catalog == "aura" then + if outfit.aura == currentOutfit.aura then + child:setChecked(true) + outfitWindow.list:moveChildToIndex(child, 1) + child:setVisible(false) + end + elseif catalog == "manabar" then + if child:getId():find(outfit.manabar) then + child:setChecked(true) + outfitWindow.list:moveChildToIndex(child, 1) + child:setVisible(false) + end + elseif catalog == "healthbar" then + if child:getId():find(outfit.healthbar) then + child:setChecked(true) + outfitWindow.list:moveChildToIndex(child, 1) + child:setVisible(false) + end + end + end + + -- color box + for j = 0, 6 do + for i = 0, 18 do + local colorBox = g_ui.createWidget("ColorBox", outfitWindow.appearance.colorBoxPanel) + local outfitColor = getOutfitColor(j * 19 + i) + colorBox:setImageColor(outfitColor) + colorBox:setId("colorBox" .. j * 19 + i) + colorBox.colorId = j * 19 + i + + if j * 19 + i == currentOutfit.head then + currentColorBox = colorBox + colorBox:setChecked(true) + end + colorBox.onCheckChange = onColorCheckChange + colorBoxes[#colorBoxes + 1] = colorBox + end + end + + -- hook outfit sections + currentClotheButtonBox = outfitWindow.appearance.parts.head + outfitWindow.appearance.parts.head.onCheckChange = onClotheCheckChange + outfitWindow.appearance.parts.primary.onCheckChange = onClotheCheckChange + outfitWindow.appearance.parts.secondary.onCheckChange = onClotheCheckChange + outfitWindow.appearance.parts.detail.onCheckChange = onClotheCheckChange + + -- previewOptions + for i, settings in ipairs(previewOptions) do + if settings.enabled then + local widget = g_ui.createWidget("OptionsCheckBox", outfitWindow.preview.options) + widget:setId(settings.id) + widget:setText(settings.text) + widget.check:setChecked(settings.checked) + + if i > 1 then + local catalog = string.sub(settings.id, 5):lower() + local data = dataTables[catalog .. "s"] + + -- if there's no options for certain category disable widget + if not data or #data == 0 then + widget.check:setChecked(false) + widget.check:setEnabled(false) + widget:setEnabled(false) + widget.check:setColor("#808080") + end + end + end + end + + -- config options + for i, settings in ipairs(configOptions) do + if settings.enabled then + local widget = g_ui.createWidget("OptionsCheckBox", outfitWindow.config.options) + widget:setId(settings.id) + widget:setText(settings.text) + widget:setChecked(settings.checked) + end + end + + -- appearance options + for i, settings in ipairs(appearanceOptions) do + if settings.enabled then + local widget = g_ui.createWidget("AppearanceCategory", outfitWindow.appearance.categories) + widget:setId(settings.id) + widget.checkBox:setText(settings.text) + widget.checkBox:setChecked(i == 2) + end + end + + setCategoryDescription("outfit", outfitType) + setCategoryDescription("mount", mountType) + + local addon1 = outfitWindow.config.options.addon1.check + local addon2 = outfitWindow.config.options.addon2.check + local mount = g_game.getFeature(GamePlayerMounts) and outfitWindow.config.options.mount.check + + if #mountList == 0 and g_game.getFeature(GamePlayerMounts) then + mount:disable() + end + + addon1:setChecked(currentAddons == 1 or currentAddons == 3) + addon2:setChecked(currentAddons > 1) + + addon1:setEnabled(availableAddons > 0) + addon2:setEnabled(availableAddons > 1) + + for i, setting in ipairs(settings) do + local outfit = setting.outfit + local mount = setting.mount + local name = setting.name + + local widget = g_ui.createWidget("LargePreviewTile", outfitWindow.list) + widget.catalog = "preset" + widget:setId("preset." .. outfit.type .. name) + widget.outfit:setOutfit(outfit) + if mount then + widget.mount:setOutfit(mount) + end + widget.title:setText(name) + end + + refreshVisiblePreviews() + refreshPreview() +end + +function destroy() + if outfitWindow then + filterText = "" + currentCategory = "outfit" + + outfitWindow:destroy() + outfitWindow = nil + end +end + +function accept() + local player = g_game.getLocalPlayer() + if outfitWindow then + save() + filterText = "" + currentCategory = "outfit" + + if g_game.getFeature(GamePlayerMounts) then + local mount = outfitWindow.config.options.mount.check:isChecked() + + if not player:isMounted() and mount then + player:mount() + end + end + + g_game.changeOutfit(getOutfitFromCurrentChecks(1)) + outfitWindow:destroy() + outfitWindow = nil + end +end + +-- json +function save() + local settings = {} + + for i, child in ipairs(outfitWindow.list:getChildren()) do + if child.catalog == "preset" then + local data = { + outfit = child.outfit:getOutfit(), + mount = child.mount:getOutfit(), + name = child.title:getText() + } + + table.insert(settings, data) + end + end + + local file = "/settings/outfits.json" + + if not g_resources.fileExists(file) then + g_resources.makeDir("/settings") + end + + local status, result = + pcall( + function() + return json.encode(settings, 2) + end + ) + if not status then + return onError("Error while saving top bar settings. Data won't be saved. Details: " .. result) + end + + if result:len() > 100 * 1024 * 1024 then + return onError("Something went wrong, file is above 100MB, won't be saved") + end + + g_resources.writeFileContents(file, result) +end + +function load() + local file = "/settings/outfits.json" + + if not g_resources.fileExists(file) then + g_resources.makeDir("/settings") + end + + if g_resources.fileExists(file) then + local status, result = + pcall( + function() + return json.decode(g_resources.readFileContents(file)) + end + ) + if not status then + return onError("Error while reading top bar settings file. To fix this problem you can delete storage.json. Details: " .. result) + end + settings = result + else + settings = {} + end +end diff --git a/800OTClient/modules/game_outfit/outfit.otmod b/800OTClient/modules/game_outfit/outfit.otmod new file mode 100644 index 0000000..dfaba8d --- /dev/null +++ b/800OTClient/modules/game_outfit/outfit.otmod @@ -0,0 +1,9 @@ +Module + name: game_outfit + description: Change local player outfit + author: Vithrax + discord: Vithrax#5814 + sandboxed: true + scripts: [ outfit ] + @onLoad: init() + @onUnload: terminate() \ No newline at end of file diff --git a/800OTClient/modules/game_outfit/outfitwindow.otui b/800OTClient/modules/game_outfit/outfitwindow.otui new file mode 100644 index 0000000..9ce9537 --- /dev/null +++ b/800OTClient/modules/game_outfit/outfitwindow.otui @@ -0,0 +1,3 @@ +OutfitWindow + @onEnter: modules.game_outfit.accept() + @onEscape: self:hide() \ No newline at end of file diff --git a/800OTClient/modules/game_playerdeath/deathwindow.otui b/800OTClient/modules/game_playerdeath/deathwindow.otui new file mode 100644 index 0000000..4f185bf --- /dev/null +++ b/800OTClient/modules/game_playerdeath/deathwindow.otui @@ -0,0 +1,30 @@ +DeathWindow < MainWindow + id: deathWindow + !text: tr('You are dead') + &baseWidth: 350 + &baseHeight: 15 + + Label + id: labelText + width: 550 + height: 140 + anchors.left: parent.left + anchors.top: parent.top + margin-left: 10 + margin-top: 2 + + Button + id: buttonOk + !text: tr('Ok') + width: 64 + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-left: 160 + + Button + id: buttonCancel + !text: tr('Cancel') + width: 64 + anchors.left: prev.right + anchors.bottom: parent.bottom + margin-left: 5 diff --git a/800OTClient/modules/game_playerdeath/playerdeath.lua b/800OTClient/modules/game_playerdeath/playerdeath.lua new file mode 100644 index 0000000..736f86e --- /dev/null +++ b/800OTClient/modules/game_playerdeath/playerdeath.lua @@ -0,0 +1,86 @@ +deathWindow = nil + +local deathTexts = { + regular = {text = 'Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nSimply click on Ok to resume your journeys!', height = 140, width = 0}, + unfair = {text = 'Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nThis death penalty has been reduced by %i%%\nbecause it was an unfair fight.\n\nSimply click on Ok to resume your journeys!', height = 185, width = 0}, + blessed = {text = 'Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back into this world\n\nThis death penalty has been reduced by 100%\nbecause you are blessed with the Adventurer\'s Blessing\n\nSimply click on Ok to resume your journeys!', height = 170, width = 90} +} + +function init() + g_ui.importStyle('deathwindow') + + connect(g_game, { onDeath = display, + onGameEnd = reset }) +end + +function terminate() + disconnect(g_game, { onDeath = display, + onGameEnd = reset }) + + reset() +end + +function reset() + if deathWindow then + deathWindow:destroy() + deathWindow = nil + end +end + +function display(deathType, penalty) + displayDeadMessage() + openWindow(deathType, penalty) +end + +function displayDeadMessage() + local advanceLabel = modules.game_interface.getRootPanel():recursiveGetChildById('middleCenterLabel') + if advanceLabel:isVisible() then return end + + modules.game_textmessage.displayGameMessage(tr('You are dead.')) +end + +function openWindow(deathType, penalty) + if deathWindow then + deathWindow:destroy() + return + end + + deathWindow = g_ui.createWidget('DeathWindow', rootWidget) + + local textLabel = deathWindow:getChildById('labelText') + if deathType == DeathType.Regular then + if penalty == 100 then + textLabel:setText(deathTexts.regular.text) + deathWindow:setHeight(deathWindow.baseHeight + deathTexts.regular.height) + deathWindow:setWidth(deathWindow.baseWidth + deathTexts.regular.width) + else + textLabel:setText(string.format(deathTexts.unfair.text, 100 - penalty)) + deathWindow:setHeight(deathWindow.baseHeight + deathTexts.unfair.height) + deathWindow:setWidth(deathWindow.baseWidth + deathTexts.unfair.width) + end + elseif deathType == DeathType.Blessed then + textLabel:setText(deathTexts.blessed.text) + deathWindow:setHeight(deathWindow.baseHeight + deathTexts.blessed.height) + deathWindow:setWidth(deathWindow.baseWidth + deathTexts.blessed.width) + end + + local okButton = deathWindow:getChildById('buttonOk') + local cancelButton = deathWindow:getChildById('buttonCancel') + + local okFunc = function() + CharacterList.doLogin() + okButton:getParent():destroy() + deathWindow = nil + end + local cancelFunc = function() + g_game.safeLogout() + cancelButton:getParent():destroy() + deathWindow = nil + end + + deathWindow.onEnter = okFunc + deathWindow.onEscape = cancelFunc + + okButton.onClick = okFunc + cancelButton.onClick = cancelFunc +end diff --git a/800OTClient/modules/game_playerdeath/playerdeath.otmod b/800OTClient/modules/game_playerdeath/playerdeath.otmod new file mode 100644 index 0000000..d8dcbb5 --- /dev/null +++ b/800OTClient/modules/game_playerdeath/playerdeath.otmod @@ -0,0 +1,9 @@ +Module + name: game_playerdeath + description: Manage player deaths + author: BeniS, edubart + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ playerdeath ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_playermount/playermount.lua b/800OTClient/modules/game_playermount/playermount.lua new file mode 100644 index 0000000..391d19f --- /dev/null +++ b/800OTClient/modules/game_playermount/playermount.lua @@ -0,0 +1,48 @@ +function init() + connect(g_game, { + onGameStart = online, + onGameEnd = offline + }) + if g_game.isOnline() then online() end +end + +function terminate() + disconnect(g_game, { + onGameStart = online, + onGameEnd = offline + }) + offline() +end + +function online() + if g_game.getFeature(GamePlayerMounts) then + g_keyboard.bindKeyDown('Ctrl+R', toggleMount) + end +end + +function offline() + if g_game.getFeature(GamePlayerMounts) then + g_keyboard.unbindKeyDown('Ctrl+R') + end +end + +function toggleMount() + local player = g_game.getLocalPlayer() + if player then + player:toggleMount() + end +end + +function mount() + local player = g_game.getLocalPlayer() + if player then + player:mount() + end +end + +function dismount() + local player = g_game.getLocalPlayer() + if player then + player:dismount() + end +end diff --git a/800OTClient/modules/game_playermount/playermount.otmod b/800OTClient/modules/game_playermount/playermount.otmod new file mode 100644 index 0000000..987e265 --- /dev/null +++ b/800OTClient/modules/game_playermount/playermount.otmod @@ -0,0 +1,9 @@ +Module + name: game_playermount + description: Manage player mounts + author: BeniS + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ playermount ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_playertrade/playertrade.lua b/800OTClient/modules/game_playertrade/playertrade.lua new file mode 100644 index 0000000..4a39327 --- /dev/null +++ b/800OTClient/modules/game_playertrade/playertrade.lua @@ -0,0 +1,81 @@ +tradeWindow = nil + +function init() + g_ui.importStyle('tradewindow') + + connect(g_game, { onOwnTrade = onGameOwnTrade, + onCounterTrade = onGameCounterTrade, + onCloseTrade = onGameCloseTrade, + onGameEnd = onGameCloseTrade }) +end + +function terminate() + disconnect(g_game, { onOwnTrade = onGameOwnTrade, + onCounterTrade = onGameCounterTrade, + onCloseTrade = onGameCloseTrade, + onGameEnd = onGameCloseTrade }) + + if tradeWindow then + tradeWindow:destroy() + end +end + +function createTrade() + tradeWindow = g_ui.createWidget('TradeWindow', modules.game_interface.getRightPanel()) + tradeWindow.onClose = function() + g_game.rejectTrade() + tradeWindow:hide() + end + tradeWindow:setup() +end + +function fillTrade(name, items, counter) + if not tradeWindow then + createTrade() + end + + local tradeItemWidget = tradeWindow:getChildById('tradeItem') + tradeItemWidget:setItemId(items[1]:getId()) + + local tradeContainer + local label + local countLabel + if counter then + tradeContainer = tradeWindow:recursiveGetChildById('counterTradeContainer') + label = tradeWindow:recursiveGetChildById('counterTradeLabel') + countLabel = tradeWindow:recursiveGetChildById('counterTradeCountLabel') + tradeWindow:recursiveGetChildById('acceptButton'):enable() + else + tradeContainer = tradeWindow:recursiveGetChildById('ownTradeContainer') + label = tradeWindow:recursiveGetChildById('ownTradeLabel') + countLabel = tradeWindow:recursiveGetChildById('ownTradeCountLabel') + end + label:setText(name) + countLabel:setText(tr("Items") .. ": " .. #items) + + + for index,item in ipairs(items) do + local itemWidget = g_ui.createWidget('Item', tradeContainer) + itemWidget:setItem(item) + itemWidget:setVirtual(true) + itemWidget:setMargin(0) + itemWidget.onClick = function() + g_game.inspectTrade(counter, index-1) + end + end +end + +function onGameOwnTrade(name, items) + fillTrade(name, items, false) +end + +function onGameCounterTrade(name, items) + fillTrade(name, items, true) +end + +function onGameCloseTrade() + if tradeWindow then + tradeWindow:destroy() + tradeWindow = nil + end +end diff --git a/800OTClient/modules/game_playertrade/playertrade.otmod b/800OTClient/modules/game_playertrade/playertrade.otmod new file mode 100644 index 0000000..ff670d1 --- /dev/null +++ b/800OTClient/modules/game_playertrade/playertrade.otmod @@ -0,0 +1,9 @@ +Module + name: game_playertrade + description: Allow to trade items with players + author: edubart + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ playertrade ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_playertrade/tradewindow.otui b/800OTClient/modules/game_playertrade/tradewindow.otui new file mode 100644 index 0000000..da5c1ab --- /dev/null +++ b/800OTClient/modules/game_playertrade/tradewindow.otui @@ -0,0 +1,112 @@ +TradeWindow < MiniWindow + !text: tr('Trade') + height: 150 + + UIItem + id: tradeItem + virtual: true + size: 16 16 + anchors.top: parent.top + anchors.left: parent.left + margin-top: 4 + margin-left: 4 + + MiniWindowContents + padding: 2 + + Label + id: ownTradeLabel + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.horizontalCenter + + Label + id: counterTradeLabel + anchors.top: parent.top + anchors.left: parent.horizontalCenter + anchors.right: parent.right + + Label + id: ownTradeCountLabel + anchors.top: ownTradeLabel.bottom + anchors.left: ownTradeLabel.left + anchors.right: ownTradeLabel.right + font: verdana-9px-bold + text-align: center + + Label + id: counterTradeCountLabel + anchors.top: counterTradeLabel.bottom + anchors.left: counterTradeLabel.left + anchors.right: counterTradeLabel.right + font: verdana-9px-bold + text-align: center + + ScrollableFlatPanel + id: ownTradeContainer + anchors.top: ownTradeCountLabel.bottom + anchors.bottom: acceptButton.top + anchors.left: ownTradeCountLabel.left + anchors.right: ownTradeCountLabel.right + margin-bottom: 3 + padding: 2 + layout: + type: grid + cell-size: 34 34 + flow: true + cell-spacing: 1 + vertical-scrollbar: ownTradeScrollBar + + VerticalScrollBar + id: ownTradeScrollBar + anchors.top: ownTradeContainer.top + anchors.bottom: ownTradeContainer.bottom + anchors.right: parent.horizontalCenter + step: 14 + pixels-scroll: true + $!on: + width: 0 + + ScrollableFlatPanel + id: counterTradeContainer + anchors.top: counterTradeCountLabel.bottom + anchors.bottom: acceptButton.top + anchors.left: counterTradeCountLabel.left + anchors.right: counterTradeCountLabel.right + margin-bottom: 3 + padding: 2 + layout: + type: grid + cell-size: 34 34 + flow: true + cell-spacing: 1 + vertical-scrollbar: counterTradeScrollBar + + VerticalScrollBar + id: counterTradeScrollBar + anchors.top: counterTradeContainer.top + anchors.bottom: counterTradeContainer.bottom + anchors.right: parent.right + step: 14 + pixels-scroll: true + $!on: + width: 0 + + Button + !text: tr('Accept') + id: acceptButton + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.horizontalCenter + margin-right: 2 + enabled: false + @onClick: g_game.acceptTrade(); self:disable() + + Button + !text: tr('Reject') + id: rejectButton + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.horizontalCenter + margin-left: 2 + @onClick: g_game.rejectTrade() diff --git a/800OTClient/modules/game_prey/prey.lua b/800OTClient/modules/game_prey/prey.lua new file mode 100644 index 0000000..ef54d82 --- /dev/null +++ b/800OTClient/modules/game_prey/prey.lua @@ -0,0 +1,541 @@ +-- sponsored by kivera-global.com +-- remade by Vithrax#5814 + +preyWindow = nil +preyButton = nil +local preyTrackerButton +local msgWindow +local bankGold = 0 +local inventoryGold = 0 +local rerollPrice = 0 +local bonusRerolls = 0 + +local PREY_BONUS_DAMAGE_BOOST = 0 +local PREY_BONUS_DAMAGE_REDUCTION = 1 +local PREY_BONUS_XP_BONUS = 2 +local PREY_BONUS_IMPROVED_LOOT = 3 +local PREY_BONUS_NONE = 4 + +local PREY_ACTION_LISTREROLL = 0 +local PREY_ACTION_BONUSREROLL = 1 +local PREY_ACTION_MONSTERSELECTION = 2 +local PREY_ACTION_REQUEST_ALL_MONSTERS = 3 +local PREY_ACTION_CHANGE_FROM_ALL = 4 +local PREY_ACTION_LOCK_PREY = 5 + +local preyDescription = {} + + +function bonusDescription(bonusType, bonusValue, bonusGrade) + if bonusType == PREY_BONUS_DAMAGE_BOOST then + return "Damage bonus (" .. bonusGrade .. "/10)" + elseif bonusType == PREY_BONUS_DAMAGE_REDUCTION then + return "Damage reduction bonus (" .. bonusGrade .. "/10)" + elseif bonusType == PREY_BONUS_XP_BONUS then + return "XP bonus (" .. bonusGrade .. "/10)" + elseif bonusType == PREY_BONUS_IMPROVED_LOOT then + return "Loot bonus (" .. bonusGrade .. "/10)" + elseif bonusType == PREY_BONUS_DAMAGE_BOOST then + return "-" + end + return "Uknown bonus" +end + +function timeleftTranslation(timeleft, forPreyTimeleft) -- in seconds + if timeleft == 0 then + if forPreyTimeleft then + return tr("infinite bonus") + end + return tr("Free") + end + local hours = string.format("%02.f", math.floor(timeleft/3600)) + local mins = string.format("%02.f", math.floor(timeleft/60 - (hours*60))) + return hours .. ":" .. mins +end + +function init() + connect(g_game, { + onGameStart = check, + onGameEnd = hide, + onResourceBalance = onResourceBalance, + onPreyFreeRolls = onPreyFreeRolls, + onPreyTimeLeft = onPreyTimeLeft, + onPreyPrice = onPreyPrice, + onPreyLocked = onPreyLocked, + onPreyInactive = onPreyInactive, + onPreyActive = onPreyActive, + onPreySelection = onPreySelection + }) + + preyWindow = g_ui.displayUI('prey') + preyWindow:hide() + preyTracker = g_ui.createWidget('PreyTracker', modules.game_interface.getRightPanel()) + preyTracker:setup() + preyTracker:setContentMaximumHeight(100) + preyTracker:setContentMinimumHeight(47) + preyTracker:hide() + if g_game.isOnline() then + check() + end + setUnsupportedSettings() +end + +local descriptionTable = { + ["shopPermButton"] = "Go to the Store to purchase the Permanent Prey Slot. Once you have completed the purchase, you can activate a prey here, no matter if your character is on a free or a Premium account.", + ["shopTempButton"] = "You can activate this prey whenever your account has Premium Status.", + ["preyWindow"] = "", + ["noBonusIcon"] = "This prey is not available for your character yet.\nCheck the large blue button(s) to learn how to unlock this prey slot", + ["selectPrey"] = "Click here to get a bonus with a higher value. The bonus for your prey will be selected randomly from one of the following: damage boost, damage reduction, bonus XP, improved loot. Your prey will be active for 2 hours hunting time again. Your prey creature will stay the same.", + ["pickSpecificPrey"] = "Available only for protocols 12+", + ["rerollButton"] = "If you like to select another prey crature, click here to get a new list with 9 creatures to choose from.\nThe newly selected prey will be active for 2 hours hunting time again.", + ["preyCandidate"] = "Select a new prey creature for the next 2 hours hunting time.", + ["choosePreyButton"] = "Click on this button to confirm selected monsters as your prey creature for the next 2 hours hunting time." +} + +function onHover(widget) + if type(widget) == "string" then + return preyWindow.description:setText(descriptionTable[widget]) + elseif type(widget) == "number" then + local slot = "slot" .. (widget + 1) + local tracker = preyTracker.contentsPanel[slot] + local desc = tracker.time:getTooltip() + desc = desc:sub(1,desc:len()-46) + return preyWindow.description:setText(desc) + end + if widget:isVisible() then + local id = widget:getId() + local desc = descriptionTable[id] + if desc then + preyWindow.description:setText(desc) + end + end +end + +function terminate() + disconnect(g_game, { + onGameStart = check, + onGameEnd = hide, + onResourceBalance = onResourceBalance, + onPreyFreeRolls = onPreyFreeRolls, + onPreyTimeLeft = onPreyTimeLeft, + onPreyPrice = onPreyPrice, + onPreyLocked = onPreyLocked, + onPreyInactive = onPreyInactive, + onPreyActive = onPreyActive, + onPreySelection = onPreySelection + }) + + if preyButton then + preyButton:destroy() + end + if preyTrackerButton then + preyTrackerButton:destroy() + end + preyWindow:destroy() + preyTracker:destroy() + if msgWindow then + msgWindow:destroy() + msgWindow = nil + end +end + +local n = 0 +function setUnsupportedSettings() + local t = {"slot1", "slot2", "slot3"} + for i, slot in pairs(t) do + local panel = preyWindow[slot] + for j, state in pairs({panel.active, panel.inactive}) do + state.select.price.text:setText("-------") + end + panel.active.autoRerollPrice.text:setText("-") + panel.active.lockPreyPrice.text:setText("-") + panel.active.choose.price.text:setText(1) + panel.active.autoReroll.autoRerollCheck:disable() + panel.active.lockPrey.lockPreyCheck:disable() + end +end + +function check() + if g_game.getFeature(GamePrey) then + if not preyButton then + preyButton = modules.client_topmenu.addRightGameToggleButton('preyButton', tr('Prey Dialog'), '/images/topbuttons/prey_window', toggle) + end + if not preyTrackerButton then + preyTrackerButton = modules.client_topmenu.addRightGameToggleButton("preyTrackerButton", tr('Prey Tracker'), '/images/topbuttons/prey', toggleTracker) + end + elseif preyButton then + preyButton:destroy() + preyButton = nil + end +end + +function toggleTracker() + if preyTracker:isVisible() then + preyTracker:hide() + else + preyTracker:show() + end +end + +function hide() + preyWindow:hide() + if msgWindow then + msgWindow:destroy() + msgWindow = nil + end +end + +function show() + if not g_game.getFeature(GamePrey) then + return hide() + end + preyWindow:show() + preyWindow:raise() + preyWindow:focus() + g_game.preyRequest() -- update preys, it's for tibia 12 +end + +function toggle() + if preyWindow:isVisible() then + return hide() + end + show() +end + +function onPreyFreeRolls(slot, timeleft) + local prey = preyWindow["slot" .. (slot + 1)] + local percent = (timeleft / (20 * 60)) * 100 + local desc = timeleftTranslation(timeleft * 60) + if not prey then return end + for i, panel in pairs({prey.active, prey.inactive}) do + local progressBar = panel.reroll.button.time + local price = panel.reroll.price.text + progressBar:setPercent(percent) + progressBar:setText(desc) + if timeleft == 0 then + price:setText("0") + end + end +end + +function onPreyTimeLeft(slot, timeLeft) + -- description + preyDescription[slot] = preyDescription[slot] or {one = "", two = ""} + local text = preyDescription[slot].one .. timeleftTranslation(timeLeft, true) .. preyDescription[slot].two + -- tracker + local percent = (timeLeft / (2 * 60 * 60)) * 100 + slot = "slot" .. (slot + 1) + local tracker = preyTracker.contentsPanel[slot] + tracker.time:setPercent(percent) + tracker.time:setTooltip(text) + for i, element in pairs({tracker.creatureName, tracker.creature, tracker.preyType, tracker.time}) do + element:setTooltip(text) + element.onClick = function() + show() + end + end + -- main window + local prey = preyWindow[slot] + if not prey then return end + local progressbar = prey.active.creatureAndBonus.timeLeft + local desc = timeleftTranslation(timeLeft, true) + progressbar:setPercent(percent) + progressbar:setText(desc) +end + +function onPreyPrice(price) + rerollPrice = price + local t = {"slot1", "slot2", "slot3"} + for i, slot in pairs(t) do + local panel = preyWindow[slot] + for j, state in pairs({panel.active, panel.inactive}) do + local price = state.reroll.price.text + local progressBar = state.reroll.button.time + if progressBar:getText() ~= "Free" then + price:setText(comma_value(rerollPrice)) + else + price:setText("0") + progressBar:setPercent(0) + end + end + end +end + +function setTimeUntilFreeReroll(slot, timeUntilFreeReroll) -- minutes + local prey = preyWindow["slot"..(slot + 1)] + if not prey then return end + local percent = (timeUntilFreeReroll / (20 * 60)) * 100 + local desc = timeleftTranslation(timeUntilFreeReroll * 60) + for i, panel in pairs({prey.active, prey.inactive}) do + local reroll = panel.reroll.button.time + reroll:setPercent(percent) + reroll:setText(desc) + local price = panel.reroll.price.text + if timeUntilFreeReroll > 0 then + price:setText(comma_value(rerollPrice)) + else + price:setText("Free") + end + end +end + +function onPreyLocked(slot, unlockState, timeUntilFreeReroll) + -- tracker + slot = "slot" .. (slot + 1) + local tracker = preyTracker.contentsPanel[slot] + if tracker then + tracker:hide() + preyTracker:setContentMaximumHeight(preyTracker:getHeight()-20) + end + -- main window + local prey = preyWindow[slot] + if not prey then return end + prey.title:setText("Locked") + prey.inactive:hide() + prey.active:hide() + prey.locked:show() +end + +function onPreyInactive(slot, timeUntilFreeReroll) + -- tracker + local tracker = preyTracker.contentsPanel["slot"..(slot + 1)] + if tracker then + tracker.creature:hide() + tracker.noCreature:show() + tracker.creatureName:setText("Inactive") + tracker.time:setPercent(0) + tracker.preyType:setImageSource("/images/game/prey/prey_no_bonus") + for i, element in pairs({tracker.creatureName, tracker.creature, tracker.preyType, tracker.time}) do + element:setTooltip("Inactive Prey. \n\nClick in this window to open the prey dialog.") + element.onClick = function() + show() + end + end + end + -- main window + setTimeUntilFreeReroll(slot, timeUntilFreeReroll) + local prey = preyWindow["slot"..(slot + 1)] + if not prey then return end + prey.active:hide() + prey.locked:hide() + prey.inactive:show() + local rerollButton = prey.inactive.reroll.button.rerollButton + rerollButton:setImageSource("/images/game/prey/prey_reroll_blocked") + rerollButton:disable() + rerollButton.onClick = function() + g_game.preyAction(slot, PREY_ACTION_LISTREROLL, 0) + end +end + +function setBonusGradeStars(slot, grade) + local prey = preyWindow["slot"..(slot + 1)] + local gradePanel = prey.active.creatureAndBonus.bonus.grade + + gradePanel:destroyChildren() + for i=1,10 do + if i <= grade then + local widget = g_ui.createWidget("Star", gradePanel) + widget.onHoverChange = function(widget,hovered) + onHover(slot) + end + else + local widget = g_ui.createWidget("NoStar", gradePanel) + widget.onHoverChange = function(widget,hovered) + onHover(slot) + end + end + end +end + +function getBigIconPath(bonusType) + local path = "/images/game/prey/" + if bonusType == PREY_BONUS_DAMAGE_BOOST then + return path.."prey_bigdamage" + elseif bonusType == PREY_BONUS_DAMAGE_REDUCTION then + return path.."prey_bigdefense" + elseif bonusType == PREY_BONUS_XP_BONUS then + return path.."prey_bigxp" + elseif bonusType == PREY_BONUS_IMPROVED_LOOT then + return path.."prey_bigloot" + end +end + +function getSmallIconPath(bonusType) + local path = "/images/game/prey/" + if bonusType == PREY_BONUS_DAMAGE_BOOST then + return path.."prey_damage" + elseif bonusType == PREY_BONUS_DAMAGE_REDUCTION then + return path.."prey_defense" + elseif bonusType == PREY_BONUS_XP_BONUS then + return path.."prey_xp" + elseif bonusType == PREY_BONUS_IMPROVED_LOOT then + return path.."prey_loot" + end +end + +function getBonusDescription(bonusType) + if bonusType == PREY_BONUS_DAMAGE_BOOST then + return "Damage Boost" + elseif bonusType == PREY_BONUS_DAMAGE_REDUCTION then + return "Damage Reduction" + elseif bonusType == PREY_BONUS_XP_BONUS then + return "XP Bonus" + elseif bonusType == PREY_BONUS_IMPROVED_LOOT then + return "Improved Loot" + end +end + +function getTooltipBonusDescription(bonusType, bonusValue) + if bonusType == PREY_BONUS_DAMAGE_BOOST then + return "You deal +"..bonusValue.."% extra damage against your prey creature." + elseif bonusType == PREY_BONUS_DAMAGE_REDUCTION then + return "You take "..bonusValue.."% less damage from your prey creature." + elseif bonusType == PREY_BONUS_XP_BONUS then + return "Killing your prey creature rewards +"..bonusValue.."% extra XP." + elseif bonusType == PREY_BONUS_IMPROVED_LOOT then + return "Your creature has a +"..bonusValue.."% chance to drop additional loot." + end +end + +function capitalFormatStr(str) + local formatted = "" + str = string.split(str, " ") + for i, word in ipairs(str) do + formatted = formatted .. " " .. (string.gsub(word, "^%l", string.upper)) + end + return formatted:trim() +end + +function onItemBoxChecked(widget) + + for i, slot in pairs({"slot1", "slot2", "slot3"}) do + local list = preyWindow[slot].inactive.list:getChildren() + if table.find(list, widget) then + for i, child in pairs(list) do + if child ~= widget then + child:setChecked(false) + end + end + end + end + widget:setChecked(true) +end + +function onPreyActive(slot, currentHolderName, currentHolderOutfit, bonusType, bonusValue, bonusGrade, timeLeft, timeUntilFreeReroll, lockType) -- locktype always 0 for protocols <12 + local tracker = preyTracker.contentsPanel["slot"..(slot + 1)] + currentHolderName = capitalFormatStr(currentHolderName) + local percent = (timeLeft / (2 * 60 * 60)) * 100 + if tracker then + tracker.creature:show() + tracker.noCreature:hide() + tracker.creatureName:setText(currentHolderName) + tracker.creature:setOutfit(currentHolderOutfit) + tracker.preyType:setImageSource(getSmallIconPath(bonusType)) + tracker.time:setPercent(percent) + preyDescription[slot] = preyDescription[slot] or {} + preyDescription[slot].one = "Creature: "..currentHolderName .. "\nDuration: " + preyDescription[slot].two = "\nValue: " ..bonusGrade.."/10".."\nType: " .. getBonusDescription(bonusType) .. "\n"..getTooltipBonusDescription(bonusType,bonusValue).."\n\nClick in this window to open the prey dialog." + for i, element in pairs({tracker.creatureName, tracker.creature, tracker.preyType, tracker.time}) do + element:setTooltip(preyDescription[slot].one .. timeleftTranslation(timeLeft, true) .. preyDescription[slot].two) + element.onClick = function() + show() + end + end + end + local prey = preyWindow["slot" .. (slot + 1)] + if not prey then return end + prey.inactive:hide() + prey.locked:hide() + prey.active:show() + prey.title:setText(currentHolderName) + local creatureAndBonus = prey.active.creatureAndBonus + creatureAndBonus.creature:setOutfit(currentHolderOutfit) + setTimeUntilFreeReroll(slot, timeUntilFreeReroll) + creatureAndBonus.bonus.icon:setImageSource(getBigIconPath(bonusType)) + creatureAndBonus.bonus.icon.onHoverChange = function(widget, hovered) + onHover(slot) + end + setBonusGradeStars(slot, bonusGrade) + creatureAndBonus.timeLeft:setPercent(percent) + creatureAndBonus.timeLeft:setText(timeleftTranslation(timeLeft)) + -- bonus reroll + prey.active.choose.selectPrey.onClick = function() + g_game.preyAction(slot, PREY_ACTION_BONUSREROLL, 0) + end + -- creature reroll + prey.active.reroll.button.rerollButton.onClick = function() + g_game.preyAction(slot, PREY_ACTION_LISTREROLL, 0) + end +end + +function onPreySelection(slot, bonusType, bonusValue, bonusGrade, names, outfits, timeUntilFreeReroll) + -- tracker + local tracker = preyTracker.contentsPanel["slot"..(slot + 1)] + if tracker then + tracker.creature:hide() + tracker.noCreature:show() + tracker.creatureName:setText("Inactive") + tracker.time:setPercent(0) + tracker.preyType:setImageSource("/images/game/prey/prey_no_bonus") + for i, element in pairs({tracker.creatureName, tracker.creature, tracker.preyType, tracker.time}) do + element:setTooltip("Inactive Prey. \n\nClick in this window to open the prey dialog.") + element.onClick = function() + show() + end + end + end + -- main window + local prey = preyWindow["slot" .. (slot + 1)] + setTimeUntilFreeReroll(slot, timeUntilFreeReroll) + if not prey then return end + prey.active:hide() + prey.locked:hide() + prey.inactive:show() + prey.title:setText(tr("Select monster")) + local rerollButton = prey.inactive.reroll.button.rerollButton + rerollButton.onClick = function() + g_game.preyAction(slot, PREY_ACTION_LISTREROLL, 0) + end + local list = prey.inactive.list + list:destroyChildren() + for i, name in ipairs(names) do + local box = g_ui.createWidget("PreyCreatureBox", list) + name = capitalFormatStr(name) + box:setTooltip(name) + box.creature:setOutfit(outfits[i]) + end + prey.inactive.choose.choosePreyButton.onClick = function() + for i, child in pairs(list:getChildren()) do + if child:isChecked() then + return g_game.preyAction(slot, PREY_ACTION_MONSTERSELECTION, i - 1) + end + end + return showMessage(tr("Error"), tr("Select monster to proceed.")) + end +end + +function onResourceBalance(type, balance) + if type == 0 then -- bank gold + bankGold = balance + elseif type == 1 then -- inventory gold + inventoryGold = balance + elseif type == 10 then -- bonus rerolls + bonusRerolls = balance + preyWindow.wildCards:setText(balance) + end + + if type == 0 or type == 1 then + preyWindow.gold:setText(comma_value(bankGold + inventoryGold)) + end +end + +function showMessage(title, message) + if msgWindow then + msgWindow:destroy() + end + + msgWindow = displayInfoBox(title, message) + msgWindow:show() + msgWindow:raise() + msgWindow:focus() +end \ No newline at end of file diff --git a/800OTClient/modules/game_prey/prey.otmod b/800OTClient/modules/game_prey/prey.otmod new file mode 100644 index 0000000..b764726 --- /dev/null +++ b/800OTClient/modules/game_prey/prey.otmod @@ -0,0 +1,10 @@ +Module + name: game_prey + description: Preys, sponsored by kivera-global.com, remade by Vithrax#5814 + author: otclient.ovh & kivera-global.com & Vithrax#5814 + website: http://otclient.ovh + sandboxed: true + scripts: [ prey ] + dependencies: [ client_topmenu ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_prey/prey.otui b/800OTClient/modules/game_prey/prey.otui new file mode 100644 index 0000000..4e0793a --- /dev/null +++ b/800OTClient/modules/game_prey/prey.otui @@ -0,0 +1,694 @@ +LockedPreyPanel < Panel + size: 195 312 + + NoCreaturePanel + id: noCreature + anchors.top: parent.top + anchors.left: parent.left + + UIButton + id: perm + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 64 + background-color: #1c4161 + margin-top: 3 + @onClick: modules.game_shop.show() + + UIWidget + id: shopPermButton + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/prey/prey_perm_test + size: 195 67 + image-clip: 0 0 204 67 + @onClick: modules.game_shop.show() + @onHoverChange: modules.game_prey.onHover(self) + + $pressed: + image-clip: 0 67 204 67 + + + UIButton + id: temp + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 64 + background-color: #1c4161 + margin-top: 7 + @onClick: modules.game_shop.show() + + UIWidget + id: shopTempButton + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/prey/prey_temp_test + size: 195 67 + image-clip: 0 0 204 67 + @onClick: modules.game_shop.show() + @onHoverChange: modules.game_prey.onHover(self) + + $pressed: + image-clip: 0 67 204 67 + +Star < UIWidget + size: 9 10 + image-source: /images/game/prey/prey_star + +NoStar < UIWidget + size: 9 10 + image-source: /images/game/prey/prey_nostar + +NoCreaturePanel < Panel + size: 195 152 + + FlatPanel + size: 124 124 + anchors.top: parent.top + anchors.left: parent.left + image-source: /images/ui/panel_flat + image-border: 1 + + UIWidget + phantom: true + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + image-source: /images/game/prey/prey_biginactive + + FlatPanel + id: bonus + anchors.top: parent.top + anchors.left: prev.right + margin-left: 10 + anchors.bottom: prev.bottom + anchors.right: parent.right + + UIWidget + id: noBonusIcon + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + image-source: /images/game/prey/prey_bignobonus + margin-top: 3 + @onHoverChange: modules.game_prey.onHover(self) + + HorizontalSeparator + anchors.top: prev.bottom + margin-top: 2 + anchors.left: parent.left + anchors.right: parent.right + margin-left: 10 + margin-right: 10 + + Panel + id: grade + anchors.top: prev.bottom + margin-top: 3 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + padding-left: 5 + layout: + type: grid + cell-size: 9 10 + cell-spacing: 2 + num-columns: 5 + + NoStar + id: noBonusIcon + @onHoverChange: modules.game_prey.onHover(self) + + NoStar + id: noBonusIcon + @onHoverChange: modules.game_prey.onHover(self) + + NoStar + id: noBonusIcon + @onHoverChange: modules.game_prey.onHover(self) + + NoStar + id: noBonusIcon + @onHoverChange: modules.game_prey.onHover(self) + + NoStar + id: noBonusIcon + @onHoverChange: modules.game_prey.onHover(self) + + NoStar + id: noBonusIcon + @onHoverChange: modules.game_prey.onHover(self) + + NoStar + id: noBonusIcon + @onHoverChange: modules.game_prey.onHover(self) + + NoStar + id: noBonusIcon + @onHoverChange: modules.game_prey.onHover(self) + + NoStar + id: noBonusIcon + @onHoverChange: modules.game_prey.onHover(self) + + NoStar + id: noBonusIcon + @onHoverChange: modules.game_prey.onHover(self) + + UIWidget + anchors.top: prev.bottom + margin-top: 5 + anchors.left: parent.left + anchors.right: parent.right + height: 20 + background-color: #262626 + border: 1 black + id: noBonusIcon + @onHoverChange: modules.game_prey.onHover(self) + +ActivePreyPanel < Panel + size: 195 312 + + CreatureAndBonus + id: creatureAndBonus + anchors.left: parent.left + anchors.top: parent.top + margin-top: 20 + + BonusReroll + id: choose + anchors.right: parent.right + anchors.top: prev.bottom + + SelectPreyCreature + id: select + anchors.verticalCenter: prev.verticalCenter + anchors.right: prev.left + margin-right: 2 + + RerollButton + id: reroll + anchors.verticalCenter: prev.verticalCenter + anchors.right: prev.left + margin-right: 2 + + FlatPanel + id: autoReroll + size: 160 20 + anchors.top: prev.bottom + margin-top: 5 + anchors.left: parent.left + + CheckBox + id: autoRerollCheck + margin-top: 2 + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + image-source: /images/ui/panel_flat + image-border: 1 + size: 155 15 + font: verdana-11px-rounded + text: Automatic Bonus Rer... + margin-left: 5 + + CardLabel + id: autoRerollPrice + anchors.top: prev.top + anchors.left: prev.right + margin-left: 2 + anchors.bottom: prev.bottom + anchors.right: parent.right + + FlatPanel + id: lockPrey + size: 160 20 + anchors.top: prev.bottom + margin-top: 5 + anchors.left: parent.left + + CheckBox + id: lockPreyCheck + margin-top: 2 + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + image-source: /images/ui/panel_flat + image-border: 1 + size: 155 15 + font: verdana-11px-rounded + text: Lock Prey + margin-left: 5 + + CardLabel + id: lockPreyPrice + anchors.top: prev.top + anchors.left: prev.right + margin-left: 2 + anchors.bottom: prev.bottom + anchors.right: parent.right + +CreatureAndBonus < Panel + size: 195 152 + + UICreature + id: creature + phantom: true + anchors.top: parent.top + anchors.left: parent.left + size: 124 124 + image-source: /images/ui/panel_flat + image-border: 1 + padding: 5 + + FlatPanel + id: bonus + anchors.top: parent.top + anchors.left: prev.right + margin-left: 10 + anchors.bottom: prev.bottom + anchors.right: parent.right + + UIWidget + id: icon + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + image-source: /images/game/prey/prey_bignobonus + margin-top: 3 + + HorizontalSeparator + anchors.top: prev.bottom + margin-top: 2 + anchors.left: parent.left + anchors.right: parent.right + margin-left: 10 + margin-right: 10 + + Panel + id: grade + anchors.top: prev.bottom + margin-top: 3 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + padding-left: 5 + layout: + type: grid + cell-size: 9 10 + cell-spacing: 2 + num-columns: 5 + + ProgressBar + id: timeLeft + anchors.top: prev.bottom + margin-top: 5 + anchors.left: parent.left + anchors.right: parent.right + height: 20 + background-color: #C28400 + +BonusReroll < FlatPanel + padding: 2 + size: 55 92 + + UIButton + id: selectPrey + anchors.top: parent.top + margin-top: 5 + anchors.horizontalCenter: parent.horizontalCenter + image-source: /images/game/prey/prey_bonus_reroll + size: 40 55 + image-clip: 1 0 35 52 + @onHoverChange: modules.game_prey.onHover(self) + + $pressed: + image-clip: 0 52 37 54 + + CardLabel + id: price + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 21 + margin-top: 2 + +InactivePreyPanel < Panel + size: 195 312 + + ChoosePrey + id: choose + anchors.right: parent.right + anchors.bottom: parent.bottom + + SelectPreyCreature + id: select + anchors.bottom: parent.bottom + anchors.right: prev.left + margin-right: 2 + + RerollButton + id: reroll + anchors.bottom: parent.bottom + anchors.right: prev.left + margin-right: 2 + + + FlatPanel + id: list + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 20 + padding-left: 4 + padding-top: 4 + size: 195 195 + layout: + type: grid + cell-size: 60 60 + cell-spacing: 3 + num-columns: 3 + +ChoosePrey < FlatPanel + size: 55 92 + padding: 10 + + UIButton + id: choosePreyButton + anchors.fill: parent + margin-bottom: 17 + margin-top: 17 + margin-left: 3 + image-source: /images/game/prey/prey_choose + image-clip: 1 0 44 35 + @onHoverChange: modules.game_prey.onHover(self) + + $pressed: + image-clip: 0 35 45 37 + +SelectPreyCreature < FlatPanel + padding: 2 + size: 70 92 + + UIButton + id: pickSpecificPrey + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + size: 66 66 + image-source: /images/game/prey/prey_select_blocked + @onHoverChange: modules.game_prey.onHover(self) + + CardLabel + id: price + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 2 + +RerollButton < FlatPanel + padding: 2 + size: 70 92 + + FlatPanel + id: button + size: 66 66 + padding: 2 + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + + ProgressBar + id: time + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 15 + text-align: center + background-color: #C28400 + + UIButton + id: rerollButton + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: prev.top + image-source: /images/game/prey/prey_reroll + image-clip: 1 0 58 45 + @onHoverChange: modules.game_prey.onHover(self) + + $pressed: + image-clip: 0 46 60 47 + + GoldLabel + id: price + anchors.top: prev.bottom + margin-top: 2 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + +CardLabel < FlatPanel + padding: 5 + + UIWidget + id: cards + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/prey/prey_wildcard + tooltip: Prey Wildcards + + UIWidget + id: text + anchors.right: prev.left + margin-right: 5 + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + text-align: right + +GoldLabel < FlatPanel + padding: 5 + + UIWidget + id: gold + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/prey/prey_gold + tooltip: Gold Coins + + UIWidget + id: text + anchors.right: prev.left + margin-right: 5 + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + text-align: right + +PreyCreatureBox < UICheckBox + border-width: 1 + border-color: #ffffff + padding: 3 + @onClick: modules.game_prey.onItemBoxChecked(self) + + UICreature + id: creature + phantom: true + anchors.fill: parent + image-color: #ffffffff + margin-top: 3 + + $checked on: + border-color: #ffffff + + $!checked: + border-color: alpha + + $!on: + image-color: #ffffff88 + color: #aaaaaa88 + +SlotPanel < Panel + size: 210 320 + image-source: /images/ui/window + image-border: 25 + padding-top: 2 + padding-bottom: 6 + padding-left: 8 + padding-right: 8 + + Label + id: title + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-align: center + + InactivePreyPanel + id: inactive + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + + ActivePreyPanel + id: active + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + visible: false + + LockedPreyPanel + id: locked + margin-top: 20 + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + visible: false + +MainWindow + id: preyWindow + !text: tr('Preys') + size: 688 520 + @onEscape: modules.game_prey.hide() + padding: 20 + @onHoverChange: modules.game_prey.onHover(self) + + SlotPanel + id: slot1 + anchors.left: parent.left + anchors.top: parent.top + margin-top: 10 + + SlotPanel + id: slot2 + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 10 + + SlotPanel + id: slot3 + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 10 + + FlatLabel + id: description + anchors.left: slot1.left + anchors.top: slot1.bottom + anchors.right: slot3.right + anchors.bottom: bottomSep.top + margin-bottom: 10 + margin-top: 10 + text-wrap: true + + HorizontalSeparator + id: bottomSep + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: closeButton.top + margin-bottom: 10 + + Button + id: closeButton + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 45 21 + text: Close + font: cipsoftFont + @onClick: modules.game_prey.hide() + + GoldLabel + id: gold + anchors.left: bottomSep.left + anchors.verticalCenter: closeButton.verticalCenter + size: 105 20 + + CardLabel + id: wildCards + anchors.left: prev.right + margin-left: 10 + anchors.verticalCenter: closeButton.verticalCenter + size: 55 20 + + UIButton + id: openStore + anchors.left: prev.right + margin-left: 10 + size: 20 20 + anchors.verticalCenter: closeButton.verticalCenter + tooltip: Go to the Store to get more Prey Wildcards! + image-source: /images/topbuttons/shop + image-clip: 0 0 20 20 + background-color: #17354e + @onClick: modules.game_shop.show() + + $pressed: + image-clip: 0 20 20 20 + +PreyCreature < Panel + height: 22 + margin-top: 3 + + UICreature + id: creature + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + size: 20 20 + + UIWidget + id: noCreature + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + size: 20 20 + image-source: /images/game/prey/prey_inactive + + UIWidget + id: preyType + anchors.left: prev.right + margin-left: 5 + anchors.verticalCenter: prev.verticalCenter + size: 15 15 + + UIWidget + id: creatureName + anchors.left: prev.right + margin-left: 5 + anchors.top: parent.top + anchors.bottom: prev.bottom + margin-bottom: 7 + anchors.right: parent.right + text: Inactive + font: verdana-11px-rounded + text-align: left + + ProgressBar + id: time + anchors.left: prev.left + anchors.right: parent.right + anchors.bottom: parent.bottom + margin-bottom: 2 + height: 6 + background-color: #C28400 + phantom: false + +PreyTracker < MiniWindow + id: preyTracker + !text: tr('Preys') + height: 95 + icon: /images/topbuttons/prey + + MiniWindowContents + padding-left: 5 + padding-right: 5 + padding-top: 5 + layout: verticalBox + + Label + !text: tr('Prey Creatures') + font: verdana-11px-rounded + + HorizontalSeparator + margin-top: 1 + + PreyCreature + id: slot1 + margin-top: 5 + + PreyCreature + id: slot2 + + PreyCreature + id: slot3 \ No newline at end of file diff --git a/800OTClient/modules/game_protocol/protocol.lua b/800OTClient/modules/game_protocol/protocol.lua new file mode 100644 index 0000000..e4f89d8 --- /dev/null +++ b/800OTClient/modules/game_protocol/protocol.lua @@ -0,0 +1,714 @@ +local registredOpcodes = nil + +local ServerPackets = { + DailyRewardCollectionState = 0xDE, + OpenRewardWall = 0xE2, + CloseRewardWall = 0xE3, + DailyRewardBasic = 0xE4, + DailyRewardHistory = 0xE5, + RestingAreaState = 0xA9, + BestiaryData = 0xd5, + BestiaryOverview = 0xd6, + BestiaryMonsterData = 0xd7, + BestiaryCharmsData = 0xd8, + BestiaryTracker = 0xd9, + BestiaryTrackerTab = 0xB9, + OpenStashSupply = 0x29, + UpdateLootTracker = 0xCF, + UpdateTrackerAnalyzer = 0xCC, + UpdateSupplyTracker = 0xCE, + KillTracker = 0xD1, + SpecialContainer = 0x2A, + isUpdateCoinBalance = 0xF2, + UpdateCoinBalance = 0xDF, + PartyAnalyzer = 0x2B, + GameNews = 0x98, + ClientCheck = 0x63, + LootStats = 0xCF, + LootContainer = 0xC0, + TournamentLeaderBoard = 0xC5, + CyclopediaCharacterInfo = 0xDA, + Tutorial = 0xDC, + Highscores = 0xB1, + Inspection = 0x76, + TeamFinderList = 0x2D, + TeamFinderLeader = 0x2C +} + +-- Server Types +local DAILY_REWARD_TYPE_ITEM = 1 +local DAILY_REWARD_TYPE_STORAGE = 2 +local DAILY_REWARD_TYPE_PREY_REROLL = 3 +local DAILY_REWARD_TYPE_XP_BOOST = 4 + +-- Client Types +local DAILY_REWARD_SYSTEM_SKIP = 1 +local DAILY_REWARD_SYSTEM_TYPE_ONE = 1 +local DAILY_REWARD_SYSTEM_TYPE_TWO = 2 +local DAILY_REWARD_SYSTEM_TYPE_OTHER = 1 +local DAILY_REWARD_SYSTEM_TYPE_PREY_REROLL = 2 +local DAILY_REWARD_SYSTEM_TYPE_XP_BOOST = 3 + +function init() + connect(g_game, { onEnterGame = registerProtocol, + onPendingGame = registerProtocol, + onGameEnd = unregisterProtocol }) + if g_game.isOnline() then + registerProtocol() + end +end + +function terminate() + disconnect(g_game, { onEnterGame = registerProtocol, + onPendingGame = registerProtocol, + onGameEnd = unregisterProtocol }) + + unregisterProtocol() +end + +function registerProtocol() + if registredOpcodes ~= nil or not g_game.getFeature(GameTibia12Protocol) then + return + end + + registredOpcodes = {} + + registerOpcode(ServerPackets.TeamFinderLeader, function(protocol, msg) + local bool = msg:getU8() -- reset + if bool > 0 then + return -- Server internal changes + end + + msg:getU16() -- Min level + msg:getU16() -- Max level + msg:getU8() -- Vocation flag + msg:getU16() -- Slots + msg:getU16() -- Free slots + msg:getU32() -- Timestamp + local type = msg:getU8() -- Team type + msg:getU16() -- Type flag + if type == 2 then + msg:getU16() -- Hunt area + end + + local size = msg:getU16() -- Members size + for i = 1, size do + msg:getU32() -- Character id + msg:getString() -- Character name + msg:getU16() -- Character level + msg:getU8() -- Vocation + msg:getU8() -- Member type (Leader == 3) + end + end) + + registerOpcode(ServerPackets.TeamFinderList, function(protocol, msg) + msg:getU8() + local size = msg:getU32() -- List size + for i = 1, size do + msg:getU32() -- Leader Id + msg:addString() -- Leader name + msg:getU16() -- Min level + msg:getU16() -- Max level + msg:getU8() -- Vocations flag + msg:getU16() -- Slots + msg:getU16() -- Used slots + msg:getU32() -- Timestamp + local type = msg:getU8() -- Team type [1]: Boss, [2]: Hunt and [3]: Quest + msg:getU16() -- Type flag + if type == 2 then + msg:getU16() -- Hunt area + end + msg:getU8() -- Player status + end + end) + + registerOpcode(ServerPackets.Inspection, function(protocol, msg) + local bool = msg:getU8() -- IsPlayer + if g_game.getProtocolVersion() >= 1230 then + msg:getU8() + end + local size = msg:getU8() -- List + for i = 1, size do + if bool > 0 then + msg:getU8() + end + msg:getString() -- Name + readAddItem(msg) + local size_2 = msg:getU8() -- Imbuements + for u = 1, size_2 do + msg:getU16() -- Imbue + end + local size_3 = msg:getU8() -- Details + for j = 1, size_3 do + msg:getString() -- Name + msg:getString() -- Description + end + end + + if bool > 0 then + msg:getString() -- Player name + local outfit = msg:getU16() -- lookType + if outfit ~= 0 then + msg:getU8() -- lookHead + msg:getU8() -- lookBody + msg:getU8() -- lookLegs + msg:getU8() -- lookFeet + msg:getU8() -- lookAddons + else + msg:getU16() -- lookTypeEx + end + local size_4 = msg:getU8() -- Detail + for l = 1, size_4 do + msg:getString() -- Name + msg:getString() -- Description + end + end + end) + + registerOpcode(ServerPackets.Highscores, function(protocol, msg) + msg:getU8() + local size = msg:getU8() -- Worlds + for i = 1, size do + msg:getString() -- World name + end + msg:getString() -- Selected world + local size_2 = msg:getU8() -- Vocations + for u = 1, size_2 do + msg:getU32() -- Id + msg:getString() -- Name + end + msg:getU32() -- Vocation selected Id + local size_3 = msg:getU8() -- Categories + for j = 1, size_3 do + msg:getU8() -- Id + msg:getString() -- Name + end + msg:getU8() -- Category selected Id + msg:getU16() -- Pages + msg:getU16() -- Selected page + local size_4 = msg:getU8() -- Entries + for l = 1, size_4 do + msg:getU32() -- Rank + msg:getString() -- Character name + msg:getString() -- Character title + msg:getU8() -- Vocation + msg:getString() -- World + msg:getU16() -- Level + msg:getU8() -- Is player? then highlight + msg:getU64() -- Points + end + msg:getU8() + msg:getU8() + msg:getU8() + msg:getU32() -- Last update + end) + + registerOpcode(ServerPackets.Tutorial, function(protocol, msg) + msg:getU8() -- Tutorial id + end) + + registerOpcode(ServerPackets.CyclopediaCharacterInfo, function(protocol, msg) + local type = msg:getU8() + if g_game.getProtocolVersion() >= 1215 then + local error = msg:getU8() + if error > 0 then + -- [1] 'No data available at the moment.' + -- [2] 'You are not allowed to see this character's data.' + -- [3] 'You are not allowed to inspect this character.' + end + end + if type == 0 then -- Basic Information + msg:getString() -- Player name + msg:getString() -- Vocation + msg:getU16() -- Level + local outfit = msg:getU16() -- lookType + if outfit ~= 0 then + msg:getU8() -- lookHead + msg:getU8() -- lookBody + msg:getU8() -- lookLegs + msg:getU8() -- lookFeet + msg:getU8() -- lookAddons + else + msg:getU16() -- lookTypeEx + end + msg:getU8() -- Hide stamina + if g_game.getProtocolVersion() >= 1220 then + msg:getU8() -- Personal habs + msg:getString() -- Title + end + elseif type == 1 then -- Character Stats + msg:getU64() -- Experience + msg:getU16() -- Level + msg:getU8() -- LevelPercent + msg:getU16() -- BaseXpGain + msg:getU32() -- Tournament + msg:getU16() -- Grinding + msg:getU16() -- Store XP + msg:getU16() -- Hunting + msg:getU16() -- Store XP Time + msg:getU8() -- Show store XP button (bool) + msg:getU16() -- Health + msg:getU16() -- Health max + msg:getU16() -- Mana + msg:getU16() -- Mana max + msg:getU8() -- Soul + msg:getU16() -- Stamina + msg:getU16() -- Food + msg:getU16() -- Offline training + msg:getU16() -- Speed + msg:getU16() -- Speed base + msg:getU32() -- Capacity bonus + msg:getU32() -- Capacity + msg:getU32() -- Capacity max + local size = msg:getU8() -- Skills + for i = 1, size do + msg:getU8() -- Skill id + msg:getU16() -- Skill level + msg:getU16() -- Base skill + msg:getU16() -- Base skill + msg:getU16() -- Skill percent + end + if g_game.getProtocolVersion() < 1215 then + msg:getU16() + msg:getString() -- Player name + msg:getString() -- Vocation + msg:getU16() -- Level + local outfit = msg:getU16() -- lookType + if outfit ~= 0 then + msg:getU8() -- lookHead + msg:getU8() -- lookBody + msg:getU8() -- lookLegs + msg:getU8() -- lookFeet + msg:getU8() -- lookAddons + else + msg:getU16() -- lookTypeEx + end + end + elseif type == 2 then -- Combat Stats + msg:getU16() -- Critical chance base + msg:getU16() -- Critical chance bonus + msg:getU16() -- Critical damage base + msg:getU16() -- Critical damage bonus + msg:getU16() -- Life leech chance base + msg:getU16() -- Life leech chance bonus + msg:getU16() -- Life leech amount base + msg:getU16() -- Life leech amount bonus + msg:getU16() -- Mana leech chance base + msg:getU16() -- Mana leech chance bonus + msg:getU16() -- Mana leech amount base + msg:getU16() -- Mana leech amount bonus + msg:getU8() -- Blessing amount + msg:getU8() -- Blessing max + msg:getU16() -- Attack + msg:getU8() -- Attack type + msg:getU8() -- Convert damage + msg:getU8() -- Convert damage type + msg:getU16() -- Armor + msg:getU16() -- Defense + local size = msg:getU8() -- Reductions + for i = 1, size do + msg:getU8() -- Element + msg:getU8() -- Percent + end + elseif type == 3 then -- Recent Deaths + msg:getU16() -- Page + msg:getU16() -- Page max + local size = msg:getU16() + for i = 1, size do + msg:getU32() -- Timestamp + msg:getString() -- Cause + end + elseif type == 4 then -- Recent PvP Kills + msg:getU16() -- Page + msg:getU16() -- Page max + local size = msg:getU16() + for i = 1, size do + msg:getU32() -- Timestamp + msg:getString() -- Description + msg:getU8() -- Status + end + elseif type == 5 then -- Achievements + msg:getU16() -- Points + msg:getU16() -- Secret max + local size = msg:getU16() -- Unlocked + for i = 1, size do + msg:getU16() -- Id + msg:getU32() -- Timestamp + local size_2 = msg:getU8() -- Is secret + if size_2 > 0 then + msg:getString() -- Name + msg:getString() -- Description + msg:getU8() -- Grade + end + end + elseif type == 6 then -- Item Summary + local size = msg:getU16() -- Item list size + for i = 1, size do + msg:getU16() -- Item client Id + msg:getU32() -- Item count + end + elseif type == 7 then -- Outfits and Mounts + local size = msg:getU16() -- Outfit list size + for i = 1, size do + msg:getU16() -- Id + msg:getString() -- Name + msg:getU8() -- Addon + msg:getU8() -- Category 0 = Standard, 1 = Quest, 2 = Store + msg:getU32() -- Is current ? then 1000 or 0 + end + msg:getU8() -- lookHead + msg:getU8() -- lookBody + msg:getU8() -- lookLegs + msg:getU8() -- lookFeet + + local size_2 = msg:getU16() -- Mount list size + for u = 1, size_2 do + msg:getU16() -- Id + msg:getString() -- Name + msg:getU8() -- Addon + msg:getU8() -- Category 0 = Standard, 1 = Quest, 2 = Store + msg:getU32() -- Is current ? then 1000 or 0 + end + if g_game.getProtocolVersion() >= 1260 then + msg:getU8() -- Mount lookHead + msg:getU8() -- Mount lookBody + msg:getU8() -- Mount lookLegs + msg:getU8() -- Mount lookFeet + end + elseif type == 8 then -- Store Summary + msg:getU32() -- Store XP boost time + msg:getU32() -- Daily reward XP boost time + local size = msg:getU8() -- Blessings + for i = 1, size do + msg:getString() -- Name + msg:getU8() -- Amount + end + msg:getU8() -- Prey slots + msg:getU8() -- Prey wildcard + msg:getU8() -- Instant reward + msg:getU8() -- Charm expansion + msg:getU8() -- Hireling + local size_2 = msg:getU8() -- Hireling jogs + for u = 1, size_2 do + msg:getU8() -- Job id + end + local size_3 = msg:getU8() -- Hireling outfit + for j = 1, size_3 do + msg:getU8() -- Outfit id + end + msg:getU16() -- House items + elseif type == 9 then -- Inspect + local size = msg:getU8() -- Items + for i = 1, size do + msg:getU8() -- Slot index + msg:getString() -- Item name + readAddItem(msg) + local size_2 = msg:getU8() -- Imbuements + for u = 1, size_2 do + msg:getU16() -- Imbue + end + local size_3 = msg:getU8() -- Detail + for j = 1, size_3 do + msg:getString() -- Name + msg:getString() -- Description + end + end + msg:getString() -- Player name + local outfit = msg:getU16() -- lookType + if outfit ~= 0 then + msg:getU8() -- lookHead + msg:getU8() -- lookBody + msg:getU8() -- lookLegs + msg:getU8() -- lookFeet + msg:getU8() -- lookAddons + else + msg:getU16() -- lookTypeEx + end + local size_4 = msg:getU8() -- Player detail + for k = 1, size_4 do + msg:getString() -- Name + msg:getString() -- Description + end + elseif type == 10 then -- Badges + local bool = msg:getU8() -- Show account + if bool > 0 then + msg:getU8() -- Is online + msg:getU8() -- Is premium + msg:getString() -- Loyality title + local size = msg:getU8() -- Badges + for i = 1, size do + msg:getU32() -- Id + msg:getString() -- Name + end + end + elseif type == 11 then -- Titles + msg:getU8() -- Title + local size = msg:getU8() -- Titles + for i = 1, size do + msg:getU8() -- Id + msg:getString() -- Name + msg:getString() -- Description + msg:getU8() -- Permanent + msg:getU8() -- Unlocked + end + end + end) + + registerOpcode(ServerPackets.TournamentLeaderBoard, function(protocol, msg) + msg:getU16() + local capacity = msg:getU8() -- Worlds + for i = 1, capacity do + msg:getString() -- World name + end + + msg:getString() -- World selected + msg:getU16() -- Refresh rate + msg:getU16() -- Current page + msg:getU16() -- Total pages + local size = msg:getU8() -- Players on page + for u = 1, size do + msg:getU32() -- Rank + msg:getU32() -- Previous rank + msg:getString() -- Name + msg:getU8() -- Vocation + msg:getU64() -- Points + msg:getU8() -- Rank chance direction (arrow0 + msg:getU8() -- Rank chance bool + end + msg:getU8() + msg:getString() -- Rewards + end) + + registerOpcode(ServerPackets.LootContainer, function(protocol, msg) + msg:getU8() -- Fallback + local size = msg:getU8() -- Quickloot size + for i = 1, size do + msg:getU8() -- Category Id + msg:getU16() -- Client Id + end + end) + + registerOpcode(ServerPackets.LootStats, function(protocol, msg) + readAddItem(msg) + msg:getString() -- Item name + end) + + registerOpcode(ServerPackets.ClientCheck, function(protocol, msg) + local size = msg:getU32() -- Data size + for i = 1, size do + msg:getU8() -- Data + end + end) + + registerOpcode(ServerPackets.GameNews, function(protocol, msg) + msg:getU32() -- Category + msg:getU8() -- Page + end) + + registerOpcode(ServerPackets.PartyAnalyzer, function(protocol, msg) + msg:getU32() -- Timestamp + msg:getU32() -- Party leader id + msg:getU8() -- Price type (client/market) + local size = msg:getU8() -- Party size + for i = 1, size do + msg:getU32() -- Player ID + msg:getU8() -- (Highlight text bool) + + msg:getU64() -- Loot count + msg:getU64() -- Supply count + msg:getU64() -- Impact count + msg:getU64() -- Heal count + end + + msg:getU8() + local size_2 = msg:getU8() -- Size + for u = 1, size_2 do + msg:getU32() -- Player ID + msg:getString() -- Player name + end + end) + + registerOpcode(ServerPackets.UpdateCoinBalance, function(protocol, msg) + msg:getU8() -- Is updating + msg:getU32() -- Normal coin + msg:getU32() -- Transferable coin + if g_game.getProtocolVersion() >= 1220 then + msg:getU32() -- Reserved auction coin + msg:getU32() -- Tournament coin + end + end) + + registerOpcode(ServerPackets.isUpdateCoinBalance, function(protocol, msg) + msg:getU8() -- Is updating + end) + + registerOpcode(ServerPackets.SpecialContainer, function(protocol, msg) + local supplyStashMenu = msg:getU8() -- ('Stow item', 'Stow container' ...) + local marketMenu = msg:getU8() -- ('Show in market') + end) + + registerOpcode(ServerPackets.KillTracker, function(protocol, msg) + msg:getString() -- Name + msg:getU16() -- lookType + msg:getU8() -- lookHead + msg:getU8() -- lookBody + msg:getU8() -- lookLegs + msg:getU8() -- lookFeet + msg:getU8() -- lookAddons + local size = msg:getU8() -- Corpse size + if size > 0 then + for i = 1, size do + readAddItem(msg) + end + end + end) + + registerOpcode(ServerPackets.UpdateSupplyTracker, function(protocol, msg) + msg:getU16() -- Item client ID + end) + + registerOpcode(ServerPackets.UpdateTrackerAnalyzer, function(protocol, msg) + local type = msg:getU8() + msg:getU32() -- Amount + if type > 0 then -- ANALYZER_DAMAGE_DEALT + msg:getU8() -- Element + if type > 1 then -- + msg:getString() -- Target + end + end + end) + + registerOpcode(ServerPackets.UpdateLootTracker, function(protocol, msg) + readAddItem(msg) + msg:getString() -- Item name + end) + + registerOpcode(ServerPackets.OpenStashSupply, function(protocol, msg) + local count = msg:getU16() -- List size + for i = 1, count do + msg:getU16() -- Item client ID + msg:getU32() -- Item count + end + + msg:getU16() -- Stash size left (total - used) + end) + + registerOpcode(ServerPackets.OpenRewardWall, function(protocol, msg) + msg:getU8() + msg:getU32() + msg:getU8() + local taken = msg:getU8() + if taken > 0 then + msg:getString() + end + msg:getU32() + msg:getU16() + msg:getU16() + end) + + registerOpcode(ServerPackets.CloseRewardWall, function(protocol, msg) + + end) + + registerOpcode(ServerPackets.DailyRewardBasic, function(protocol, msg) + local count = msg:getU8() + for i = 1, count do + readDailyReward(msg) + readDailyReward(msg) + end + local maxBonus = msg:getU8() + for i = 1, maxBonus do + msg:getString() + msg:getU8() + end + msg:getU8() + end) + + registerOpcode(ServerPackets.DailyRewardHistory, function(protocol, msg) + local count = msg:getU8() + for i=1,count do + msg:getU32() + msg:getU8() + msg:getString() + msg:getU16() + end + end) + + registerOpcode(ServerPackets.BestiaryTrackerTab, function(protocol, msg) + local count = msg:getU8() + for i = 1, count do + msg:getU16() + msg:getU32() + msg:getU16() + msg:getU16() + msg:getU16() + msg:getU8() + end + end) + + +end + +function readAddItem(msg) + msg:getU16() -- Item client ID + + if g_game.getProtocolVersion() < 1150 then + msg:getU8() -- Unmarked + end + + local var = msg:getU8() + if g_game.getProtocolVersion() > 1150 then + if var == 1 then + msg:getU32() -- Loot flag + end + + if g_game.getProtocolVersion() >= 1260 then + local isQuiver = msg:getU8() + if isQuiver == 1 then + msg:getU32() -- Quiver count + end + end + else + msg:getU8() + end +end + +function unregisterProtocol() + if registredOpcodes == nil then + return + end + for _, opcode in ipairs(registredOpcodes) do + ProtocolGame.unregisterOpcode(opcode) + end + registredOpcodes = nil +end + +function registerOpcode(code, func) + if registredOpcodes[code] ~= nil then + error("Duplicated registed opcode: " .. code) + end + registredOpcodes[code] = func + ProtocolGame.registerOpcode(code, func) +end + +function readDailyReward(msg) + local systemType = msg:getU8() + if (systemType == 1) then + msg:getU8() + local count = msg:getU8() + for i = 1, count do + msg:getU16() + msg:getString() + msg:getU32() + end + elseif (systemType == 2) then + msg:getU8() + local type = msg:getU8() + + if (type == DAILY_REWARD_SYSTEM_TYPE_PREY_REROLL) then + msg:getU8() + elseif (type == DAILY_REWARD_SYSTEM_TYPE_XP_BOOST) then + msg:getU16() + end + end +end diff --git a/800OTClient/modules/game_protocol/protocol.otmod b/800OTClient/modules/game_protocol/protocol.otmod new file mode 100644 index 0000000..81238ac --- /dev/null +++ b/800OTClient/modules/game_protocol/protocol.otmod @@ -0,0 +1,11 @@ +Module + name: game_protocol + description: Game protocol + author: otclientv8 + website: http://otclient.ovh + scripts: [ protocol ] + sandboxed: true + autoload: true + autoload-priority: 500 + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_questlog/questlog.lua b/800OTClient/modules/game_questlog/questlog.lua new file mode 100644 index 0000000..f391edd --- /dev/null +++ b/800OTClient/modules/game_questlog/questlog.lua @@ -0,0 +1,288 @@ +questLogButton = nil +questTrackerButton = nil +window = nil +trackerWindow = nil +settings = {} + +local callDelay = 1000 -- each call delay is also increased by random values (0-callDelay/2) +local dispatcher = {} + +function init() + g_ui.importStyle('questlogwindow') + + window = g_ui.createWidget('QuestLogWindow', rootWidget) + window:hide() + trackerWindow = g_ui.createWidget('QuestTracker', modules.game_interface.getRightPanel()) + trackerWindow:setup() + trackerWindow:hide() + + if not g_app.isMobile() then + questLogButton = modules.client_topmenu.addLeftGameButton('questLogButton', tr('Quest Log'), '/images/topbuttons/questlog', function() g_game.requestQuestLog() end, false, 8) + questTrackerButton = modules.client_topmenu.addLeftGameButton('questTrackerButton', tr('Quest Tracker'), '/images/topbuttons/quest_tracker', toggle, false, 9) + end + + connect(g_game, { onQuestLog = onGameQuestLog, + onQuestLine = onGameQuestLine, + onGameEnd = offline, + onGameStart = online}) + online() +end + +function terminate() + disconnect(g_game, { onQuestLog = onGameQuestLog, + onQuestLine = onGameQuestLine, + onGameEnd = offline, + onGameStart = online}) + + offline() + if questLogButton then + questLogButton:destroy() + end + if questTrackerButton then + questTrackerButton:destroy() + end +end + +function toggle() + if trackerWindow:isVisible() then + trackerWindow:hide() + else + trackerWindow:show() + end +end + +function offline() + if window then + window:hide() + end + if trackerWindow then + trackerWindow:hide() + end + save() + -- reset tracker + trackerWindow.contentsPanel.list:destroyChildren() + trackerWindow.contentsPanel.list:setHeight(0) +end + +function online() + local playerName = g_game.getCharacterName() + if not playerName then return end -- just to be sure + load() + refreshQuests() + refreshTrackerWidgets() + + local playerName = g_game.getCharacterName() + settings[playerName] = settings[playerName] or {} + local settings = settings[playerName] + local missionList = window.missionlog.missionList + local track = window.missionlog.track + local missionDescription = window.missionlog.missionDescription + + connect(missionList, { + onChildFocusChange = function(self, focusedChild) + if focusedChild == nil then return end + missionDescription:setText(focusedChild.description) + if focusedChild:isVisible() then + track:setEnabled(true) + end + track:setChecked(settings[focusedChild.trackData]) + end + } + ) +end + +function show(questlog) + if questlog then + window:raise() + window:show() + window:focus() + window.missionlog.currentQuest = nil -- reset current quest + window.questlog:setVisible(true) + window.missionlog:setVisible(false) + window.closeButton:setText('Close') + window.showButton:setVisible(true) + window.missionlog.track:setEnabled(false) + window.missionlog.track:setChecked(false) + window.missionlog.missionDescription:setText('') + else + window.questlog:setVisible(false) + window.missionlog:setVisible(true) + window.closeButton:setText('Back') + window.showButton:setVisible(false) + end +end + +function back() + if window:isVisible() then + if window.questlog:isVisible() then + window:hide() + else + show(true) + end + end +end + +function showQuestLine() + local questList = window.questlog.questList + local child = questList:getFocusedChild() + + g_game.requestQuestLine(child.questId) + window.missionlog.questName:setText(child.questName) + window.missionlog.currentQuest = child.questId +end + +function onGameQuestLog(quests) + show(true) + + local questList = window.questlog.questList + + questList:destroyChildren() + for i,questEntry in pairs(quests) do + local id, name, completed = unpack(questEntry) + + local questLabel = g_ui.createWidget('QuestLabel', questList) + questLabel:setChecked(i % 2 == 0) + questLabel.questId = id -- for quest tracker + questLabel.questName = name + name = completed and name.." (completed)" or name + questLabel:setText(name) + questLabel.onDoubleClick = function() + window.missionlog.currentQuest = id + g_game.requestQuestLine(id) + window.missionlog.questName:setText(questLabel.questName) + end + end + questList:focusChild(questList:getFirstChild()) +end + +function onGameQuestLine(questId, questMissions) + show(false) + local missionList = window.missionlog.missionList + + if questId == window.missionlog.currentQuest then + missionList:destroyChildren() + end + for i,questMission in pairs(questMissions) do + local name, description = unpack(questMission) + + --questlog + local missionLabel = g_ui.createWidget('QuestLabel', missionList) + local widgetId = questId..'.'..i + missionLabel:setChecked(i % 2 == 0) + missionLabel:setId(widgetId) + missionLabel.questId = questId + missionLabel.trackData = widgetId + missionLabel:setText(name) + missionLabel.description = description + missionLabel:setVisible(questId == window.missionlog.currentQuest) + + --tracker + local trackerLabel = trackerWindow.contentsPanel.list[widgetId] + trackerLabel = trackerLabel or g_ui.createWidget('QuestTrackerLabel', trackerWindow.contentsPanel.list) + trackerLabel:setId(widgetId) + trackerLabel.description:setText(description) + local data = settings[g_game.getCharacterName()] + trackerLabel:setVisible(description:len() > 0 and data[widgetId]) + end + local focusTarget = missionList:getFirstChild() + if focusTarget and focusTarget:isVisible() then + missionList:focusChild(focusTarget) + end +end + +function onTrackOptionChange(checkbox) + local newStatus = not checkbox:isChecked() + checkbox:setChecked(newStatus) + + local missionList = window.missionlog.missionList + local focused = missionList:getFocusedChild() + if not focused then return end + local settings = settings[g_game.getCharacterName()] + local trackdata = focused.trackData + + -- settings + settings[trackdata] = newStatus + + local trackerWidget = trackerWindow.contentsPanel.list[trackdata] + if trackerWidget then + trackerWidget:setVisible(newStatus) + end + + refreshQuests() + save() +end + +function refreshQuests() + if not g_game.isOnline() then return end + local data = settings[g_game.getCharacterName()] + data = data or {} + + -- do not execute when questlost is in use + if not window:isVisible() then + for questData, track in pairs(data) do + local id = string.split(questData, ".")[1] + + if not track then + dispatcher[questData] = nil -- remove from dispatcher if no longer tracked + else + dispatcher[questData] = dispatcher[questData] or g_clock.millis() + end + + if dispatcher[questData] and g_clock.millis() > dispatcher[questData] + callDelay + math.random(callDelay/2) then + dispatcher[questData] = g_clock.millis() + scheduleEvent(function() + g_game.requestQuestLine(id) -- request update + end, math.random(callDelay/2) ) + end + end + end + + scheduleEvent(refreshQuests, callDelay) +end + +function refreshTrackerWidgets() + if not g_game.isOnline() then return end + local data = settings[g_game.getCharacterName()] + data = data or {} + + for questData, enabled in pairs(data) do + local data = string.split(questData, ".") + local id = tonumber(data[1]) + + local widget = trackerWindow.contentsPanel.list[questData] + if not widget then + g_game.requestQuestLine(id) + end + end +end + +-- json handlers +function load() + local file = "/settings/questlog.json" + if g_resources.fileExists(file) then + local status, result = pcall(function() + return json.decode(g_resources.readFileContents(file)) + end) + if not status then + return g_logger.error( + "Error while reading profiles file. To fix this problem you can delete storage.json. Details: " .. + result) + end + settings = result + end +end + +function save() + local file = "/settings/questlog.json" + local status, result = pcall(function() return json.encode(settings, 2) end) + if not status then + return g_logger.error( + "Error while saving profile settings. Data won't be saved. Details: " .. + result) + end + if result:len() > 100 * 1024 * 1024 then + return g_logger.error( + "Something went wrong, file is above 100MB, won't be saved") + end + g_resources.writeFileContents(file, result) +end \ No newline at end of file diff --git a/800OTClient/modules/game_questlog/questlog.otmod b/800OTClient/modules/game_questlog/questlog.otmod new file mode 100644 index 0000000..04b7aa7 --- /dev/null +++ b/800OTClient/modules/game_questlog/questlog.otmod @@ -0,0 +1,9 @@ +Module + name: game_questlog + description: Quest status preview and tracking + author: Vithrax + website: https://github.com/Vithrax + sandboxed: true + scripts: [ questlog ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_questlog/questlogwindow.otui b/800OTClient/modules/game_questlog/questlogwindow.otui new file mode 100644 index 0000000..8304eca --- /dev/null +++ b/800OTClient/modules/game_questlog/questlogwindow.otui @@ -0,0 +1,188 @@ +QuestTrackerLabel < Panel + height: 20 + layout: + type: verticalBox + fit-children: true + + $!first: + margin-top: 3 + + Label + id: description + text-align: center + text-wrap: true + text-auto-resize: true + + HorizontalSeparator + margin-top: 3 + +QuestLabel < Label + font: verdana-11px-monochrome + height: 18 + text-offset: 2 1 + focusable: true + color: #aaaaaa + background-color: #484848 + + $checked: + background-color: #414141 + + $focus: + background-color: #ffffff22 + +QuestLog < Panel + TextList + id: questList + anchors.fill: parent + margin-bottom: 20 + focusable: false + background-color: #484848 + vertical-scrollbar: questListScrollBar + + VerticalScrollBar + id: questListScrollBar + anchors.top: questList.top + anchors.bottom: questList.bottom + anchors.right: questList.right + step: 14 + pixels-scroll: true + +MissionLog < Panel + Label + id: questName + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-align: left + text: questline name + + TextList + id: missionList + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 3 + height: 133 + padding: 1 + focusable: false + vertical-scrollbar: missionListScrollBar + background-color: #484848 + + VerticalScrollBar + id: missionListScrollBar + anchors.top: missionList.top + anchors.right: missionList.right + anchors.bottom: missionList.bottom + step: 14 + pixels-scroll: true + + CheckBox + id: track + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-bottom: 25 + !text: tr('Show in quest tracker') + @onClick: modules.game_questlog.onTrackOptionChange(self) + enabled: false + + FlatLabel + id: missionDescription + anchors.top: missionList.bottom + anchors.left: parent.left + anchors.right: missionListScrollBar.right + anchors.bottom: prev.top + background-color: #363636 + margin-bottom: 10 + margin-top: 10 + text-wrap: true + +QuestLogWindow < MainWindow + id: questLogWindow + !text: tr('Quest Log') + size: 330 405 + @onEscape: modules.game_questlog.back() + $mobile: + size: 330 350 + + QuestLog + id: questlog + anchors.top: parent.top + anchors.bottom: bottomSep.top + anchors.left: parent.left + anchors.right: parent.right + visible: false + + MissionLog + id: missionlog + anchors.fill: prev + + HorizontalSeparator + id: bottomSep + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: closeButton.top + margin-bottom: 8 + + Button + id: closeButton + !text: tr('Close') + font: cipsoftFont + anchors.right: parent.right + anchors.bottom: parent.bottom + color: #ffffff + size: 45 21 + @onClick: modules.game_questlog.back() + + Button + id: showButton + anchors.verticalCenter: prev.verticalCenter + anchors.right: prev.left + margin-right: 3 + color: #ffffff + size: 45 21 + !text: tr('Show') + font: cipsoftFont + @onClick: modules.game_questlog.showQuestLine() + + Button + id: trackerButton + anchors.verticalCenter: prev.verticalCenter + anchors.left: parent.left + margin-right: 3 + color: #ffffff + size: 80 21 + text-align: center + !text: tr('Quest Tracker') + font: cipsoftFont + @onClick: modules.game_questlog.toggle() + +QuestTracker < MiniWindow + id: questTracker + !text: tr('Quest Tracker') + height: 60 + icon: /images/topbuttons/quest_tracker + + MiniWindowContents + padding-left: 5 + padding-right: 5 + padding-top: 5 + layout: verticalBox + + Panel + id: list + layout: + type: verticalBox + fit-children: true + + Panel + height: 20 + margin-top: 5 + Button + !text: tr('Add Tracked Quest') + anchors.fill: parent + margin-left: 30 + margin-right: 30 + font: cipsoftFont + color: #FFFFFF + @onClick: g_game.requestQuestLog() \ No newline at end of file diff --git a/800OTClient/modules/game_ruleviolation/ruleviolation.lua b/800OTClient/modules/game_ruleviolation/ruleviolation.lua new file mode 100644 index 0000000..156fdde --- /dev/null +++ b/800OTClient/modules/game_ruleviolation/ruleviolation.lua @@ -0,0 +1,151 @@ +rvreasons = {} +rvreasons[1] = tr("1a) Offensive Name") +rvreasons[2] = tr("1b) Invalid Name Format") +rvreasons[3] = tr("1c) Unsuitable Name") +rvreasons[4] = tr("1d) Name Inciting Rule Violation") +rvreasons[5] = tr("2a) Offensive Statement") +rvreasons[6] = tr("2b) Spamming") +rvreasons[7] = tr("2c) Illegal Advertising") +rvreasons[8] = tr("2d) Off-Topic Public Statement") +rvreasons[9] = tr("2e) Non-English Public Statement") +rvreasons[10] = tr("2f) Inciting Rule Violation") +rvreasons[11] = tr("3a) Bug Abuse") +rvreasons[12] = tr("3b) Game Weakness Abuse") +rvreasons[13] = tr("3c) Using Unofficial Software to Play") +rvreasons[14] = tr("3d) Hacking") +rvreasons[15] = tr("3e) Multi-Clienting") +rvreasons[16] = tr("3f) Account Trading or Sharing") +rvreasons[17] = tr("4a) Threatening Gamemaster") +rvreasons[18] = tr("4b) Pretending to Have Influence on Rule Enforcement") +rvreasons[19] = tr("4c) False Report to Gamemaster") +rvreasons[20] = tr("Destructive Behaviour") +rvreasons[21] = tr("Excessive Unjustified Player Killing") + +rvactions = {} +rvactions[0] = tr("Notation") +rvactions[1] = tr("Name Report") +rvactions[2] = tr("Banishment") +rvactions[3] = tr("Name Report + Banishment") +rvactions[4] = tr("Banishment + Final Warning") +rvactions[5] = tr("Name Report + Banishment + Final Warning") +rvactions[6] = tr("Statement Report") + +ruleViolationWindow = nil +reasonsTextList = nil +actionsTextList = nil + +function init() + connect(g_game, { onGMActions = loadReasons }) + + ruleViolationWindow = g_ui.displayUI('ruleviolation') + ruleViolationWindow:setVisible(false) + + reasonsTextList = ruleViolationWindow:getChildById('reasonList') + actionsTextList = ruleViolationWindow:getChildById('actionList') + + g_keyboard.bindKeyDown('Ctrl+Y', function() show() end) + + if g_game.isOnline() then + loadReasons() + end +end + +function terminate() + disconnect(g_game, { onGMActions = loadReasons }) + g_keyboard.unbindKeyDown('Ctrl+Y') + + ruleViolationWindow:destroy() +end + +function hasWindowAccess() + return reasonsTextList:getChildCount() > 0 +end + +function loadReasons() + reasonsTextList:destroyChildren() + actionsTextList:destroyChildren() + + local actions = g_game.getGMActions() + for reason, actionFlags in pairs(actions) do + local label = g_ui.createWidget('RVListLabel', reasonsTextList) + label.onFocusChange = onSelectReason + label:setText(rvreasons[reason]) + label.reasonId = reason + label.actionFlags = actionFlags + end + + if not hasWindowAccess() and ruleViolationWindow:isVisible() then hide() end +end + +function show(target, statement) + if g_game.isOnline() and hasWindowAccess() then + if target then + ruleViolationWindow:getChildById('nameText'):setText(target) + end + + if statement then + ruleViolationWindow:getChildById('statementText'):setText(statement) + end + + ruleViolationWindow:show() + ruleViolationWindow:raise() + ruleViolationWindow:focus() + ruleViolationWindow:getChildById('commentText'):focus() + end +end + +function hide() + ruleViolationWindow:hide() + clearForm() +end + +function onSelectReason(reasonLabel, focused) + if reasonLabel.actionFlags and focused then + actionsTextList:destroyChildren() + for actionBaseFlag = 0, #rvactions do + local actionFlagString = rvactions[actionBaseFlag] + if bit32.band(reasonLabel.actionFlags, math.pow(2, actionBaseFlag)) > 0 then + local label = g_ui.createWidget('RVListLabel', actionsTextList) + label:setText(actionFlagString) + label.actionId = actionBaseFlag + end + end + end +end + +function report() + local reasonLabel = reasonsTextList:getFocusedChild() + if not reasonLabel then + displayErrorBox(tr("Error"), tr("You must select a reason.")) + return + end + + local actionLabel = actionsTextList:getFocusedChild() + if not actionLabel then + displayErrorBox(tr("Error"), tr("You must select an action.")) + return + end + + local target = ruleViolationWindow:getChildById('nameText'):getText() + local reason = reasonLabel.reasonId + local action = actionLabel.actionId + local comment = ruleViolationWindow:getChildById('commentText'):getText() + local statement = ruleViolationWindow:getChildById('statementText'):getText() + local statementId = 0 -- TODO: message unique id ? + local ipBanishment = ruleViolationWindow:getChildById('ipBanCheckBox'):isChecked() + if action == 6 and statement == "" then + displayErrorBox(tr("Error"), tr("No statement has been selected.")) + elseif comment == "" then + displayErrorBox(tr("Error"), tr("You must enter a comment.")) + else + g_game.reportRuleViolation(target, reason, action, comment, statement, statementId, ipBanishment) + hide() + end +end + +function clearForm() + ruleViolationWindow:getChildById('nameText'):clearText() + ruleViolationWindow:getChildById('commentText'):clearText() + ruleViolationWindow:getChildById('statementText'):clearText() + ruleViolationWindow:getChildById('ipBanCheckBox'):setChecked(false) +end diff --git a/800OTClient/modules/game_ruleviolation/ruleviolation.otmod b/800OTClient/modules/game_ruleviolation/ruleviolation.otmod new file mode 100644 index 0000000..4b5f4fd --- /dev/null +++ b/800OTClient/modules/game_ruleviolation/ruleviolation.otmod @@ -0,0 +1,9 @@ +Module + name: game_ruleviolation + description: Rule violation interface (Ctrl+Y) + author: andrefaramir + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ ruleviolation ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_ruleviolation/ruleviolation.otui b/800OTClient/modules/game_ruleviolation/ruleviolation.otui new file mode 100644 index 0000000..570c118 --- /dev/null +++ b/800OTClient/modules/game_ruleviolation/ruleviolation.otui @@ -0,0 +1,120 @@ +RVListLabel < Label + background-color: alpha + text-offset: 2 0 + focusable: true + + $focus: + background-color: #ffffff22 + color: #ffffff + +RVLabel < Label + anchors.left: parent.left + anchors.right: parent.right + + $first: + anchors.top: parent.top + + $!first: + margin-top: 10 + anchors.top: prev.bottom + +RVTextEdit < TextEdit + margin-top: 2 + anchors.left: parent.left + anchors.right: parent.right + + $first: + anchors.top: parent.top + + $!first: + anchors.top: prev.bottom + +MainWindow + id: ruleViolationWindow + size: 400 445 + text: Rule Violation + @onEscape: hide() + + RVLabel + !text: tr('Name') .. ':' + + RVTextEdit + id: nameText + + RVLabel + !text: tr('Statement') .. ':' + + RVTextEdit + id: statementText + enabled: false + + RVLabel + !text: tr('Reason') .. ':' + + TextList + id: reasonList + height: 100 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 2 + focusable: false + vertical-scrollbar: reasonListScrollBar + + VerticalScrollBar + id: reasonListScrollBar + anchors.top: reasonList.top + anchors.bottom: reasonList.bottom + anchors.right: reasonList.right + step: 14 + pixels-scroll: true + + RVLabel + !text: tr('Action') .. ':' + + TextList + id: actionList + height: 60 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 2 + focusable: false + vertical-scrollbar: actionListScrollBar + + VerticalScrollBar + id: actionListScrollBar + anchors.top: actionList.top + anchors.bottom: actionList.bottom + anchors.right: actionList.right + step: 14 + pixels-scroll: true + + CheckBox + id: ipBanCheckBox + !text: tr('IP Address Banishment') + margin-top: 10 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + + RVLabel + !text: tr('Comment') .. ':' + + RVTextEdit + id: commentText + + Button + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: hide() + + Button + !text: tr('Ok') + width: 64 + margin-right: 5 + anchors.right: prev.left + anchors.bottom: parent.bottom + @onClick: report() \ No newline at end of file diff --git a/800OTClient/modules/game_shaders/shaders.lua b/800OTClient/modules/game_shaders/shaders.lua new file mode 100644 index 0000000..180a862 --- /dev/null +++ b/800OTClient/modules/game_shaders/shaders.lua @@ -0,0 +1,25 @@ +function init() + -- add manually your shaders from /data/shaders + + -- map shaders + g_shaders.createShader("map_default", "/shaders/map_default_vertex", "/shaders/map_default_fragment") + + g_shaders.createShader("map_rainbow", "/shaders/map_rainbow_vertex", "/shaders/map_rainbow_fragment") + g_shaders.addTexture("map_rainbow", "/images/shaders/rainbow.png") + + -- use modules.game_interface.gameMapPanel:setShader("map_rainbow") to set shader + + -- outfit shaders + g_shaders.createOutfitShader("outfit_default", "/shaders/outfit_default_vertex", "/shaders/outfit_default_fragment") + + g_shaders.createOutfitShader("outfit_rainbow", "/shaders/outfit_rainbow_vertex", "/shaders/outfit_rainbow_fragment") + g_shaders.addTexture("outfit_rainbow", "/images/shaders/rainbow.png") + + -- you can use creature:setOutfitShader("outfit_rainbow") to set shader + +end + +function terminate() +end + + diff --git a/800OTClient/modules/game_shaders/shaders.otmod b/800OTClient/modules/game_shaders/shaders.otmod new file mode 100644 index 0000000..0c6d727 --- /dev/null +++ b/800OTClient/modules/game_shaders/shaders.otmod @@ -0,0 +1,9 @@ +Module + name: game_shaders + description: Load shaders + author: otclientv8 + website: http://otclient.ovh + scripts: [ shaders ] + sandboxed: true + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_shop/shop.lua b/800OTClient/modules/game_shop/shop.lua new file mode 100644 index 0000000..3f7ca69 --- /dev/null +++ b/800OTClient/modules/game_shop/shop.lua @@ -0,0 +1,618 @@ +-- private variables +local SHOP_EXTENTED_OPCODE = 201 + +shop = nil +transferWindow = nil +local otcv8shop = false +local shopButton = nil +local msgWindow = nil +local browsingHistory = false +local transferValue = 0 + +-- for classic store +local storeUrl = "" +local coinsPacketSize = 0 + +local CATEGORIES = {} +local HISTORY = {} +local STATUS = {} +local AD = {} + +local selectedOffer = {} + +local function sendAction(action, data) + if not g_game.getFeature(GameExtendedOpcode) then + return + end + + local protocolGame = g_game.getProtocolGame() + if data == nil then + data = {} + end + if protocolGame then + protocolGame:sendExtendedJSONOpcode(SHOP_EXTENTED_OPCODE, {action = action, data = data}) + end +end + +-- public functions +function init() + connect(g_game, { + onGameStart = check, + onGameEnd = hide, + onStoreInit = onStoreInit, + onStoreCategories = onStoreCategories, + onStoreOffers = onStoreOffers, + onStoreTransactionHistory = onStoreTransactionHistory, + onStorePurchase = onStorePurchase, + onStoreError = onStoreError, + onCoinBalance = onCoinBalance + }) + + ProtocolGame.registerExtendedJSONOpcode(SHOP_EXTENTED_OPCODE, onExtendedJSONOpcode) + + if g_game.isOnline() then + check() + end + createShop() + createTransferWindow() +end + +function terminate() + disconnect(g_game, { + onGameStart = check, + onGameEnd = hide, + onStoreInit = onStoreInit, + onStoreCategories = onStoreCategories, + onStoreOffers = onStoreOffers, + onStoreTransactionHistory = onStoreTransactionHistory, + onStorePurchase = onStorePurchase, + onStoreError = onStoreError, + onCoinBalance = onCoinBalance + }) + + ProtocolGame.unregisterExtendedJSONOpcode(SHOP_EXTENTED_OPCODE, onExtendedJSONOpcode) + + if shopButton then + shopButton:destroy() + shopButton = nil + end + if shop then + disconnect(shop.categories, { onChildFocusChange = changeCategory }) + shop:destroy() + shop = nil + end + if msgWindow then + msgWindow:destroy() + end +end + +function check() + otcv8shop = false + sendAction("init") +end + +function hide() + if not shop then + return + end + shop:hide() +end + +function show() + if not shop or not shopButton then + return + end + if g_game.getFeature(GameIngameStore) then + g_game.openStore(0) + end + + shop:show() + shop:raise() + shop:focus() +end + +function softHide() + if not transferWindow then return end + + transferWindow:hide() + shop:show() +end + +function showTransfer() + if not shop or not transferWindow then return end + + hide() + transferWindow:show() + transferWindow:raise() + transferWindow:focus() +end + +function hideTransfer() + if not shop or not transferWindow then return end + + transferWindow:hide() + show() +end + +function toggle() + if not shop then + return + end + if shop:isVisible() then + return hide() + end + show() + check() +end + +function createShop() + if shop then return end + shop = g_ui.displayUI('shop') + shop:hide() + shopButton = modules.client_topmenu.addRightGameToggleButton('shopButton', tr('Shop'), '/images/topbuttons/shop', toggle, false, 8) + connect(shop.categories, { onChildFocusChange = changeCategory }) +end + +function createTransferWindow() + if transferWindow then return end + transferWindow = g_ui.displayUI('transfer') + transferWindow:hide() +end + +function onStoreInit(url, coins) + if otcv8shop then return end + storeUrl = url + if storeUrl:len() > 0 then + if storeUrl:sub(storeUrl:len(), storeUrl:len()) ~= "/" then + storeUrl = storeUrl .. "/" + end + storeUrl = storeUrl .. "64/" + if storeUrl:sub(1, 4):lower() ~= "http" then + storeUrl = "http://" .. storeUrl + end + end + coinsPacketSize = coins + createShop() + createTransferWindow() +end + +function onStoreCategories(categories) + if not shop or otcv8shop then return end + local correctCategories = {} + for i, category in ipairs(categories) do + local image = "" + if category.icon:len() > 0 then + image = storeUrl .. category.icon + end + table.insert(correctCategories, { + type = "image", + image = image, + name = category.name, + offers = {} + }) + end + processCategories(correctCategories) +end + +function onStoreOffers(categoryName, offers) + if not shop or otcv8shop then return end + local updated = false + + for i, category in ipairs(CATEGORIES) do + if category.name == categoryName then + if #category.offers ~= #offers then + updated = true + end + for i=1,#category.offers do + if category.offers[i].title ~= offers[i].name or category.offers[i].id ~= offers[i].id or category.offers[i].cost ~= offers[i].price then + updated = true + end + end + if updated then + for offer in pairs(category.offers) do + category.offers[offer] = nil + end + for i, offer in ipairs(offers) do + local image = "" + if offer.icon:len() > 0 then + image = storeUrl .. offer.icon + end + table.insert(category.offers, { + id=offer.id, + type="image", + image=image, + cost=offer.price, + title=offer.name, + description=offer.description + }) + end + end + end + end + if not updated then + return + end + + local activeCategory = shop.categories:getFocusedChild() + changeCategory(activeCategory, activeCategory) +end + +function onStoreTransactionHistory(currentPage, hasNextPage, offers) + if not shop or otcv8shop then return end + HISTORY = {} + for i, offer in ipairs(offers) do + table.insert(HISTORY, { + id=offer.id, + type="image", + image=storeUrl .. offer.icon, + cost=offer.price, + title=offer.name, + description=offer.description + }) + end + + if not browsingHistory then return end + clearOffers() + shop.categories:focusChild(nil) + for i, transaction in ipairs(HISTORY) do + addOffer(0, transaction) + end +end + +function onStorePurchase(message) + if not shop or otcv8shop then return end + if not transferWindow:isVisible() then + processMessage({title="Successful shop purchase", msg=message}) + else + processMessage({title="Successfuly gifted coins", msg=message}) + softHide() + end +end + +function onStoreError(errorType, message) + if not shop or otcv8shop then return end + if not transferWindow:isVisible() then + processMessage({title="Shop Error", msg=message}) + else + processMessage({title="Gift coins error", msg=message}) + end +end + +function onCoinBalance(coins, transferableCoins) + if not shop or otcv8shop then return end + shop.infoPanel.points:setText(tr("Points:") .. " " .. coins) + transferWindow.coinsBalance:setText(tr('Transferable Tibia Coins: ') .. coins) + transferWindow.coinsAmount:setMaximum(coins) + shop.infoPanel.buy:hide() + shop.infoPanel:setHeight(20) +end + +function transferCoins() + if not transferWindow then return end + local amount = 0 + amount = transferWindow.coinsAmount:getValue() + local recipient = transferWindow.recipient:getText() + + g_game.transferCoins(recipient, amount) + transferWindow.recipient:setText('') + transferWindow.coinsAmount:setValue(0) +end + +function onExtendedJSONOpcode(protocol, code, json_data) + createShop() + createTransferWindow() + + local action = json_data['action'] + local data = json_data['data'] + local status = json_data['status'] + if not action or not data then + return false + end + + otcv8shop = true + if action == 'categories' then + processCategories(data) + elseif action == 'history' then + processHistory(data) + elseif action == 'message' then + processMessage(data) + end + + if status then + processStatus(status) + end +end + +function clearOffers() + while shop.offers:getChildCount() > 0 do + local child = shop.offers:getLastChild() + shop.offers:destroyChildren(child) + end +end + +function clearCategories() + CATEGORIES = {} + clearOffers() + while shop.categories:getChildCount() > 0 do + local child = shop.categories:getLastChild() + shop.categories:destroyChildren(child) + end +end + +function clearHistory() + HISTORY = {} + if browsingHistory then + clearOffers() + end +end + +function processCategories(data) + if table.equal(CATEGORIES,data) then + return + end + clearCategories() + CATEGORIES = data + for i, category in ipairs(data) do + addCategory(category) + end + if not browsingHistory then + local firstCategory = shop.categories:getChildByIndex(1) + if firstCategory then + firstCategory:focus() + end + end +end + +function processHistory(data) + if table.equal(HISTORY,data) then + return + end + HISTORY = data + if browsingHistory then + showHistory(true) + end +end + +function processMessage(data) + if msgWindow then + msgWindow:destroy() + end + + local title = tr(data["title"]) + local msg = data["msg"] + msgWindow = displayInfoBox(title, msg) + msgWindow.onDestroy = function(widget) + if widget == msgWindow then + msgWindow = nil + end + end + msgWindow:show() + msgWindow:raise() + msgWindow:focus() +end + +function processStatus(data) + if table.equal(STATUS,data) then + return + end + STATUS = data + + if data['ad'] then + processAd(data['ad']) + end + if data['points'] then + shop.infoPanel.points:setText(tr("Points:") .. " " .. data['points']) + end + if data['buyUrl'] and data['buyUrl']:sub(1, 4):lower() == "http" then + shop.infoPanel.buy:show() + shop.infoPanel.buy.onMouseRelease = function() + scheduleEvent(function() g_platform.openUrl(data['buyUrl']) end, 50) + end + else + shop.infoPanel.buy:hide() + shop.infoPanel:setHeight(20) + end +end + +function processAd(data) + if table.equal(AD,data) then + return + end + AD = data + + if data['image'] and data['image']:sub(1, 4):lower() == "http" then + HTTP.downloadImage(data['image'], function(path, err) + if err then g_logger.warning("HTTP error: " .. err .. " - " .. data['image']) return end + shop.adPanel:setHeight(shop.infoPanel:getHeight()) + shop.adPanel.ad:setText("") + shop.adPanel.ad:setImageSource(path) + shop.adPanel.ad:setImageFixedRatio(true) + shop.adPanel.ad:setImageAutoResize(true) + shop.adPanel.ad:setHeight(shop.infoPanel:getHeight()) + end) + elseif data['text'] and data['text']:len() > 0 then + shop.adPanel:setHeight(shop.infoPanel:getHeight()) + shop.adPanel.ad:setText(data['text']) + shop.adPanel.ad:setHeight(shop.infoPanel:getHeight()) + else + shop.adPanel:setHeight(0) + end + if data['url'] and data['url']:sub(1, 4):lower() == "http" then + shop.adPanel.ad.onMouseRelease = function() + scheduleEvent(function() g_platform.openUrl(data['url']) end, 50) + end + else + shop.adPanel.ad.onMouseRelease = nil + end +end + +function addCategory(data) + local category + if data["type"] == "item" then + category = g_ui.createWidget('ShopCategoryItem', shop.categories) + category.item:setItemId(data["item"]) + category.item:setItemCount(data["count"]) + category.item:setShowCount(false) + elseif data["type"] == "outfit" then + category = g_ui.createWidget('ShopCategoryCreature', shop.categories) + category.creature:setOutfit(data["outfit"]) + if data["outfit"]["rotating"] then + category.creature:setAutoRotating(true) + end + elseif data["type"] == "image" then + category = g_ui.createWidget('ShopCategoryImage', shop.categories) + if data["image"] and data["image"]:sub(1, 4):lower() == "http" then + HTTP.downloadImage(data['image'], function(path, err) + if err then g_logger.warning("HTTP error: " .. err .. " - " .. data["image"]) return end + category.image:setImageSource(path) + end) + else + category.image:setImageSource(data["image"]) + end + else + g_logger.error("Invalid shop category type: " .. tostring(data["type"])) + return + end + category:setId("category_" .. shop.categories:getChildCount()) + category.name:setText(data["name"]) +end + +function showHistory(force) + if browsingHistory and not force then + return + end + + if g_game.getFeature(GameIngameStore) and not otcv8shop then + g_game.openTransactionHistory(100) + end + sendAction("history") + + browsingHistory = true + clearOffers() + shop.categories:focusChild(nil) + for i, transaction in ipairs(HISTORY) do + addOffer(0, transaction) + end +end + +function addOffer(category, data) + local offer + if data["type"] == "item" then + offer = g_ui.createWidget('ShopOfferItem', shop.offers) + offer.item:setItemId(data["item"]) + offer.item:setItemCount(data["count"]) + offer.item:setShowCount(false) + elseif data["type"] == "outfit" then + offer = g_ui.createWidget('ShopOfferCreature', shop.offers) + offer.creature:setOutfit(data["outfit"]) + if data["outfit"]["rotating"] then + offer.creature:setAutoRotating(true) + end + elseif data["type"] == "image" then + offer = g_ui.createWidget('ShopOfferImage', shop.offers) + if data["image"] and data["image"]:sub(1, 4):lower() == "http" then + HTTP.downloadImage(data['image'], function(path, err) + if err then g_logger.warning("HTTP error: " .. err .. " - " .. data['image']) return end + if not offer.image then return end + offer.image:setImageSource(path) + end) + elseif data["image"] and data["image"]:len() > 1 then + offer.image:setImageSource(data["image"]) + end + else + g_logger.error("Invalid shop offer type: " .. tostring(data["type"])) + return + end + offer:setId("offer_" .. category .. "_" .. shop.offers:getChildCount()) + offer.title:setText(data["title"] .. " (" .. data["cost"] .. " points)") + offer.description:setText(data["description"]) + offer.offerId = data["id"] + if category ~= 0 then + offer.onDoubleClick = buyOffer + offer.buyButton.onClick = function() buyOffer(offer) end + else + offer.buyButton:hide() + end +end + + +function changeCategory(widget, newCategory) + if not newCategory then + return + end + + if g_game.getFeature(GameIngameStore) and widget ~= newCategory and not otcv8shop then + local serviceType = 0 + if g_game.getFeature(GameTibia12Protocol) then + serviceType = 2 + end + g_game.requestStoreOffers(newCategory.name:getText(), serviceType) + end + + browsingHistory = false + local id = tonumber(newCategory:getId():split("_")[2]) + clearOffers() + for i, offer in ipairs(CATEGORIES[id]["offers"]) do + addOffer(id, offer) + end +end + +function buyOffer(widget) + if not widget then + return + end + local split = widget:getId():split("_") + if #split ~= 3 then + return + end + local category = tonumber(split[2]) + local offer = tonumber(split[3]) + local item = CATEGORIES[category]["offers"][offer] + if not item then + return + end + + selectedOffer = {category=category, offer=offer, title=item.title, cost=item.cost, id=widget.offerId} + + scheduleEvent(function() + if msgWindow then + msgWindow:destroy() + end + + local title = tr("Buying from shop") + local msg = "Do you want to buy " .. item.title .. " for " .. item.cost .. " premium points?" + msgWindow = displayGeneralBox(title, msg, { + { text=tr('Yes'), callback=buyConfirmed }, + { text=tr('No'), callback=buyCanceled }, + anchor=AnchorHorizontalCenter}, buyConfirmed, buyCanceled) + msgWindow:show() + msgWindow:raise() + msgWindow:focus() + msgWindow:raise() + end, 50) +end + +function buyConfirmed() + msgWindow:destroy() + msgWindow = nil + sendAction("buy", selectedOffer) + if g_game.getFeature(GameIngameStore) and selectedOffer.id and not otcv8shop then + local offerName = selectedOffer.title:lower() + if string.find(offerName, "name") and string.find(offerName, "change") and modules.client_textedit then + modules.client_textedit.singlelineEditor("", function(newName) + if newName:len() == 0 then + return + end + g_game.buyStoreOffer(selectedOffer.id, 1, newName) + end) + else + g_game.buyStoreOffer(selectedOffer.id, 0, "") + end + end +end + +function buyCanceled() + msgWindow:destroy() + msgWindow = nil + selectedOffer = {} +end \ No newline at end of file diff --git a/800OTClient/modules/game_shop/shop.otmod b/800OTClient/modules/game_shop/shop.otmod new file mode 100644 index 0000000..eec111a --- /dev/null +++ b/800OTClient/modules/game_shop/shop.otmod @@ -0,0 +1,10 @@ +Module + name: game_shop + description: Game shop + author: otclient.ovh + website: http://otclient.ovh + sandboxed: true + scripts: [ shop ] + dependencies: [ client_topmenu ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_shop/shop.otui b/800OTClient/modules/game_shop/shop.otui new file mode 100644 index 0000000..87f14e2 --- /dev/null +++ b/800OTClient/modules/game_shop/shop.otui @@ -0,0 +1,245 @@ +ShopCategory < Panel + height: 36 + focusable: true + background: alpha + + $focus: + background: #99999999 + + Label + id: name + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + margin-left: 40 + text-align: left + color: white + font: verdana-11px-rounded + +ShopCategoryItem < ShopCategory + UIItem + id: item + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-top: 2 + margin-bottom: 2 + margin-left: 2 + virtual: true + size: 32 32 + +ShopCategoryCreature < ShopCategory + UICreature + id: creature + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-top: 2 + margin-bottom: 2 + margin-left: 2 + size: 32 32 + +ShopCategoryImage < ShopCategory + Label + id: image + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-top: 2 + margin-bottom: 2 + margin-left: 2 + size: 32 32 + + + +ShopOffer < Panel + height: 56 + background: alpha + + $focus: + background: #99999999 + + Label + id: title + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + margin-top: 4 + margin-left: 55 + text-align: topleft + color: white + font: verdana-11px-rounded + + Label + id: description + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + margin-left: 55 + margin-right: 55 + text-align: topleft + text-auto-resize: true + text-wrap: true + color: white + font: verdana-11px-rounded + + Button + id: buyButton + text: BUY + height: 25 + anchors.verticalCenter: parent.verticalCenter + anchors.left: prev.right + anchors.right: parent.right + margin-right: 15 + text-align: center + +ShopOfferItem < ShopOffer + UIItem + id: item + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-top: 4 + margin-bottom: 4 + margin-left: 2 + virtual: true + size: 48 48 + +ShopOfferCreature < ShopOffer + UICreature + id: creature + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-top: 4 + margin-bottom: 4 + margin-left: 2 + size: 48 48 + +ShopOfferImage < ShopOffer + Label + id: image + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-top: 4 + margin-bottom: 4 + margin-left: 2 + size: 48 48 + +MainWindow + id: shopWindow + !text: tr('Shop') + size: 750 500 + @onEscape: modules.game_shop.hide() + $mobile: + size: 500 360 + + Panel + id: infoPanel + anchors.top: parent.top + anchors.left: parent.left + width: 230 + height: 60 + + Label + id: points + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + margin-top: 10 + text: - + text-auto-resize: true + + Button + id: buy + anchors.horizontalCenter: parent.horizontalCenter + width: 150 + anchors.top: prev.bottom + margin-top: 10 + visible: false + !text: tr("Buy points") + + Panel + id: adPanel + anchors.top: parent.top + anchors.left: infoPanel.right + anchors.right: parent.right + margin-left: 10 + height: 0 + + Label + id: ad + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.bottom: parent.bottom + text-auto-resize: true + text-wrap: true + text-align: center + font: sans-bold-16px + + TextList + id: categories + vertical-scrollbar: categoriesScrollBar + anchors.top: infoPanel.bottom + anchors.left: infoPanel.left + anchors.right: infoPanel.right + anchors.bottom: transactionHistory.top + margin-top: 10 + margin-bottom: 10 + padding: 1 + focusable: false + + VerticalScrollBar + id: categoriesScrollBar + anchors.top: categories.top + anchors.bottom: categories.bottom + anchors.right: categories.right + step: 50 + pixels-scroll: true + + TextList + id: offers + vertical-scrollbar: offersScrollBar + anchors.top: adPanel.bottom + anchors.left: adPanel.left + anchors.right: adPanel.right + anchors.bottom: transactionHistory.top + margin-top: 10 + margin-bottom: 10 + padding: 1 + focusable: false + + VerticalScrollBar + id: offersScrollBar + anchors.top: offers.top + anchors.bottom: offers.bottom + anchors.right: offers.right + step: 50 + pixels-scroll: true + + Button + id: transactionHistory + !text: tr('Transaction history') + width: 128 + anchors.left: parent.left + anchors.bottom: parent.bottom + @onClick: modules.game_shop.showHistory() + + Button + id: transferOpen + !text: tr('Transfer Coins') + width: 128 + anchors.left: prev. right + margin-left: 10 + anchors.verticalCenter: prev.verticalCenter + @onClick: modules.game_shop.showTransfer() + + Button + id: buttonCancel + !text: tr('Close') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: modules.game_shop.hide() \ No newline at end of file diff --git a/800OTClient/modules/game_shop/transfer.otui b/800OTClient/modules/game_shop/transfer.otui new file mode 100644 index 0000000..dbfefc4 --- /dev/null +++ b/800OTClient/modules/game_shop/transfer.otui @@ -0,0 +1,80 @@ +MainWindow + id: transferWindow + !text: tr('Gift Tibia Coins') + size: 280 240 + @onEscape: modules.game_shop.hideTransfer() + + Label + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-wrap: true + height: 56 + !text: tr('Please select the amount of Tibia Coins you would like to gift and enter the name of the character that should receive the Tibia Coins.') + + Label + anchors.top: prev.bottom + anchors.left: parent.left + margin-top: 20 + !text: tr('Reciepient:') + + TextEdit + id: recipient + anchors.verticalCenter: prev.verticalCenter + anchors.right: parent.right + width: 150 + text-align: left + + Label + id: coinsBalance + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 10 + text-align: center + !text: tr('Transferable Tibia Coins:') + + Label + id: coinsAmountLabel + anchors.top: prev.bottom + anchors.left: parent.left + margin-top: 20 + !text: tr('Amount to gift: ') + + SpinBox + id: coinsAmount + anchors.right: parent.right + width: 100 + anchors.verticalCenter: prev.verticalCenter + text: 0 + minimum: 0 + maximum: 0 + focusable: true + editable: true + + HorizontalSeparator + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: cancelButton.top + margin-bottom: 8 + + Button + id: cancelButton + !text: tr('Cancel') + font: cipsoftFont + anchors.right: parent.right + anchors.bottom: parent.bottom + size: 45 21 + margin-top: 15 + margin-right: 5 + @onClick: modules.game_shop.hideTransfer() + + Button + id: giftButton + !text: tr('Gift') + font: cipsoftFont + size: 45 21 + anchors.verticalCenter: prev.verticalCenter + anchors.right: prev.left + margin-right: 5 + @onClick: modules.game_shop.transferCoins() \ No newline at end of file diff --git a/800OTClient/modules/game_skills/skills.lua b/800OTClient/modules/game_skills/skills.lua new file mode 100644 index 0000000..76188e0 --- /dev/null +++ b/800OTClient/modules/game_skills/skills.lua @@ -0,0 +1,437 @@ +skillsWindow = nil +skillsButton = nil + +function init() + connect(LocalPlayer, { + onExperienceChange = onExperienceChange, + onLevelChange = onLevelChange, + onHealthChange = onHealthChange, + onManaChange = onManaChange, + onSoulChange = onSoulChange, + onFreeCapacityChange = onFreeCapacityChange, + onTotalCapacityChange = onTotalCapacityChange, + onStaminaChange = onStaminaChange, + onOfflineTrainingChange = onOfflineTrainingChange, + onRegenerationChange = onRegenerationChange, + onSpeedChange = onSpeedChange, + onBaseSpeedChange = onBaseSpeedChange, + onMagicLevelChange = onMagicLevelChange, + onBaseMagicLevelChange = onBaseMagicLevelChange, + onSkillChange = onSkillChange, + onBaseSkillChange = onBaseSkillChange + }) + connect(g_game, { + onGameStart = refresh, + onGameEnd = offline + }) + + skillsButton = modules.client_topmenu.addRightGameToggleButton('skillsButton', tr('Skills'), '/images/topbuttons/skills', toggle, false, 1) + skillsButton:setOn(true) + skillsWindow = g_ui.loadUI('skills', modules.game_interface.getRightPanel()) + + refresh() + skillsWindow:setup() +end + +function terminate() + disconnect(LocalPlayer, { + onExperienceChange = onExperienceChange, + onLevelChange = onLevelChange, + onHealthChange = onHealthChange, + onManaChange = onManaChange, + onSoulChange = onSoulChange, + onFreeCapacityChange = onFreeCapacityChange, + onTotalCapacityChange = onTotalCapacityChange, + onStaminaChange = onStaminaChange, + onOfflineTrainingChange = onOfflineTrainingChange, + onRegenerationChange = onRegenerationChange, + onSpeedChange = onSpeedChange, + onBaseSpeedChange = onBaseSpeedChange, + onMagicLevelChange = onMagicLevelChange, + onBaseMagicLevelChange = onBaseMagicLevelChange, + onSkillChange = onSkillChange, + onBaseSkillChange = onBaseSkillChange + }) + disconnect(g_game, { + onGameStart = refresh, + onGameEnd = offline + }) + + skillsWindow:destroy() + skillsButton:destroy() +end + +function expForLevel(level) + return math.floor((50*level*level*level)/3 - 100*level*level + (850*level)/3 - 200) +end + +function expToAdvance(currentLevel, currentExp) + return expForLevel(currentLevel+1) - currentExp +end + +function resetSkillColor(id) + local skill = skillsWindow:recursiveGetChildById(id) + local widget = skill:getChildById('value') + widget:setColor('#bbbbbb') +end + +function toggleSkill(id, state) + local skill = skillsWindow:recursiveGetChildById(id) + skill:setVisible(state) +end + +function setSkillBase(id, value, baseValue) + if baseValue <= 0 or value < 0 then + return + end + local skill = skillsWindow:recursiveGetChildById(id) + local widget = skill:getChildById('value') + + if value > baseValue then + widget:setColor('#008b00') -- green + skill:setTooltip(baseValue .. ' +' .. (value - baseValue)) + elseif value < baseValue then + widget:setColor('#b22222') -- red + skill:setTooltip(baseValue .. ' ' .. (value - baseValue)) + else + widget:setColor('#bbbbbb') -- default + skill:removeTooltip() + end +end + +function setSkillValue(id, value) + local skill = skillsWindow:recursiveGetChildById(id) + local widget = skill:getChildById('value') + widget:setText(value) +end + +function setSkillColor(id, value) + local skill = skillsWindow:recursiveGetChildById(id) + local widget = skill:getChildById('value') + widget:setColor(value) +end + +function setSkillTooltip(id, value) + local skill = skillsWindow:recursiveGetChildById(id) + local widget = skill:getChildById('value') + widget:setTooltip(value) +end + +function setSkillPercent(id, percent, tooltip, color) + local skill = skillsWindow:recursiveGetChildById(id) + local widget = skill:getChildById('percent') + if widget then + widget:setPercent(math.floor(percent)) + + if tooltip then + widget:setTooltip(tooltip) + end + + if color then + widget:setBackgroundColor(color) + end + end +end + +function checkAlert(id, value, maxValue, threshold, greaterThan) + if greaterThan == nil then greaterThan = false end + local alert = false + + -- maxValue can be set to false to check value and threshold + -- used for regeneration checking + if type(maxValue) == 'boolean' then + if maxValue then + return + end + + if greaterThan then + if value > threshold then + alert = true + end + else + if value < threshold then + alert = true + end + end + elseif type(maxValue) == 'number' then + if maxValue < 0 then + return + end + + local percent = math.floor((value / maxValue) * 100) + if greaterThan then + if percent > threshold then + alert = true + end + else + if percent < threshold then + alert = true + end + end + end + + if alert then + setSkillColor(id, '#b22222') -- red + else + resetSkillColor(id) + end +end + +function update() + local offlineTraining = skillsWindow:recursiveGetChildById('offlineTraining') + if not g_game.getFeature(GameOfflineTrainingTime) then + offlineTraining:hide() + else + offlineTraining:show() + end + + local regenerationTime = skillsWindow:recursiveGetChildById('regenerationTime') + if not g_game.getFeature(GamePlayerRegenerationTime) then + regenerationTime:hide() + else + regenerationTime:show() + end +end + +function refresh() + local player = g_game.getLocalPlayer() + if not player then return end + + if expSpeedEvent then expSpeedEvent:cancel() end + expSpeedEvent = cycleEvent(checkExpSpeed, 30*1000) + + onExperienceChange(player, player:getExperience()) + onLevelChange(player, player:getLevel(), player:getLevelPercent()) + onHealthChange(player, player:getHealth(), player:getMaxHealth()) + onManaChange(player, player:getMana(), player:getMaxMana()) + onSoulChange(player, player:getSoul()) + onFreeCapacityChange(player, player:getFreeCapacity()) + onStaminaChange(player, player:getStamina()) + onMagicLevelChange(player, player:getMagicLevel(), player:getMagicLevelPercent()) + onOfflineTrainingChange(player, player:getOfflineTrainingTime()) + onRegenerationChange(player, player:getRegenerationTime()) + onSpeedChange(player, player:getSpeed()) + + local hasAdditionalSkills = g_game.getFeature(GameAdditionalSkills) + for i = Skill.Fist, Skill.ManaLeechAmount do + onSkillChange(player, i, player:getSkillLevel(i), player:getSkillLevelPercent(i)) + onBaseSkillChange(player, i, player:getSkillBaseLevel(i)) + + if i > Skill.Fishing then + toggleSkill('skillId'..i, hasAdditionalSkills) + end + end + + update() + + local contentsPanel = skillsWindow:getChildById('contentsPanel') + skillsWindow:setContentMinimumHeight(44) + if hasAdditionalSkills then + skillsWindow:setContentMaximumHeight(480) + else + skillsWindow:setContentMaximumHeight(390) + end +end + +function offline() + if expSpeedEvent then expSpeedEvent:cancel() expSpeedEvent = nil end +end + +function toggle() + if skillsButton:isOn() then + skillsWindow:close() + skillsButton:setOn(false) + else + skillsWindow:open() + skillsButton:setOn(true) + end +end + +function checkExpSpeed() + local player = g_game.getLocalPlayer() + if not player then return end + + local currentExp = player:getExperience() + local currentTime = g_clock.seconds() + if player.lastExps ~= nil then + player.expSpeed = (currentExp - player.lastExps[1][1])/(currentTime - player.lastExps[1][2]) + onLevelChange(player, player:getLevel(), player:getLevelPercent()) + else + player.lastExps = {} + end + table.insert(player.lastExps, {currentExp, currentTime}) + if #player.lastExps > 30 then + table.remove(player.lastExps, 1) + end +end + +function onMiniWindowClose() + skillsButton:setOn(false) +end + +function onSkillButtonClick(button) + local percentBar = button:getChildById('percent') + if percentBar then + percentBar:setVisible(not percentBar:isVisible()) + if percentBar:isVisible() then + button:setHeight(21) + else + button:setHeight(21 - 6) + end + end +end + +function onExperienceChange(localPlayer, value) + local postFix = "" + if value > 1e15 then + postFix = "B" + value = math.floor(value / 1e9) + elseif value > 1e12 then + postFix = "M" + value = math.floor(value / 1e6) + elseif value > 1e9 then + postFix = "K" + value = math.floor(value / 1e3) + end + setSkillValue('experience', comma_value(value) .. postFix) +end + +function onLevelChange(localPlayer, value, percent) + setSkillValue('level', value) + local text = tr('You have %s percent to go', 100 - percent) .. '\n' .. + comma_value(expToAdvance(localPlayer:getLevel(), localPlayer:getExperience())) .. tr(' of experience left') + + if localPlayer.expSpeed ~= nil then + local expPerHour = math.floor(localPlayer.expSpeed * 3600) + if expPerHour > 0 then + local nextLevelExp = expForLevel(localPlayer:getLevel()+1) + local hoursLeft = (nextLevelExp - localPlayer:getExperience()) / expPerHour + local minutesLeft = math.floor((hoursLeft - math.floor(hoursLeft))*60) + hoursLeft = math.floor(hoursLeft) + text = text .. '\n' .. comma_value(expPerHour) .. ' of experience per hour' + text = text .. '\n' .. tr('Next level in %d hours and %d minutes', hoursLeft, minutesLeft) + end + end + + setSkillPercent('level', percent, text) +end + +function onHealthChange(localPlayer, health, maxHealth) + setSkillValue('health', health) + checkAlert('health', health, maxHealth, 30) +end + +function onManaChange(localPlayer, mana, maxMana) + setSkillValue('mana', mana) + checkAlert('mana', mana, maxMana, 30) +end + +function onSoulChange(localPlayer, soul) + setSkillValue('soul', soul) +end + +function onFreeCapacityChange(localPlayer, freeCapacity) + setSkillValue('capacity', freeCapacity) + checkAlert('capacity', freeCapacity, localPlayer:getTotalCapacity(), 20) +end + +function onTotalCapacityChange(localPlayer, totalCapacity) + checkAlert('capacity', localPlayer:getFreeCapacity(), totalCapacity, 20) +end + +function onStaminaChange(localPlayer, stamina) + local hours = math.floor(stamina / 60) + local minutes = stamina % 60 + if minutes < 10 then + minutes = '0' .. minutes + end + local percent = math.floor(100 * stamina / (42 * 60)) -- max is 42 hours --TODO not in all client versions + + setSkillValue('stamina', hours .. ":" .. minutes) + + --TODO not all client versions have premium time + if stamina > 2400 and g_game.getClientVersion() >= 1038 and localPlayer:isPremium() then + local text = tr("You have %s hours and %s minutes left", hours, minutes) .. '\n' .. + tr("Now you will gain 50%% more experience") + setSkillPercent('stamina', percent, text, 'green') + elseif stamina > 2400 and g_game.getClientVersion() >= 1038 and not localPlayer:isPremium() then + local text = tr("You have %s hours and %s minutes left", hours, minutes) .. '\n' .. + tr("You will not gain 50%% more experience because you aren't premium player, now you receive only 1x experience points") + setSkillPercent('stamina', percent, text, '#89F013') + elseif stamina > 2400 and g_game.getClientVersion() < 1038 then + local text = tr("You have %s hours and %s minutes left", hours, minutes) .. '\n' .. + tr("If you are premium player, you will gain 50%% more experience") + setSkillPercent('stamina', percent, text, 'green') + elseif stamina <= 2400 and stamina > 840 then + setSkillPercent('stamina', percent, tr("You have %s hours and %s minutes left", hours, minutes), 'orange') + elseif stamina <= 840 and stamina > 0 then + local text = tr("You have %s hours and %s minutes left", hours, minutes) .. "\n" .. + tr("You gain only 50%% experience and you don't may gain loot from monsters") + setSkillPercent('stamina', percent, text, 'red') + elseif stamina == 0 then + local text = tr("You have %s hours and %s minutes left", hours, minutes) .. "\n" .. + tr("You don't may receive experience and loot from monsters") + setSkillPercent('stamina', percent, text, 'black') + end +end + +function onOfflineTrainingChange(localPlayer, offlineTrainingTime) + if not g_game.getFeature(GameOfflineTrainingTime) then + return + end + local hours = math.floor(offlineTrainingTime / 60) + local minutes = offlineTrainingTime % 60 + if minutes < 10 then + minutes = '0' .. minutes + end + local percent = 100 * offlineTrainingTime / (12 * 60) -- max is 12 hours + + setSkillValue('offlineTraining', hours .. ":" .. minutes) + setSkillPercent('offlineTraining', percent, tr('You have %s percent', percent)) +end + +function onRegenerationChange(localPlayer, regenerationTime) + if not g_game.getFeature(GamePlayerRegenerationTime) or regenerationTime < 0 then + return + end + local minutes = math.floor(regenerationTime / 60) + local seconds = regenerationTime % 60 + if seconds < 10 then + seconds = '0' .. seconds + end + + setSkillValue('regenerationTime', minutes .. ":" .. seconds) + checkAlert('regenerationTime', regenerationTime, false, 300) +end + +function onSpeedChange(localPlayer, speed) + setSkillValue('speed', speed) + + onBaseSpeedChange(localPlayer, localPlayer:getBaseSpeed()) +end + +function onBaseSpeedChange(localPlayer, baseSpeed) + setSkillBase('speed', localPlayer:getSpeed(), baseSpeed) +end + +function onMagicLevelChange(localPlayer, magiclevel, percent) + setSkillValue('magiclevel', magiclevel) + setSkillPercent('magiclevel', percent, tr('You have %s percent to go', 100 - percent)) + + onBaseMagicLevelChange(localPlayer, localPlayer:getBaseMagicLevel()) +end + +function onBaseMagicLevelChange(localPlayer, baseMagicLevel) + setSkillBase('magiclevel', localPlayer:getMagicLevel(), baseMagicLevel) +end + +function onSkillChange(localPlayer, id, level, percent) + setSkillValue('skillId' .. id, level) + setSkillPercent('skillId' .. id, percent, tr('You have %s percent to go', 100 - percent)) + + onBaseSkillChange(localPlayer, id, localPlayer:getSkillBaseLevel(id)) +end + +function onBaseSkillChange(localPlayer, id, baseLevel) + setSkillBase('skillId'..id, localPlayer:getSkillLevel(id), baseLevel) +end diff --git a/800OTClient/modules/game_skills/skills.otmod b/800OTClient/modules/game_skills/skills.otmod new file mode 100644 index 0000000..fdf4fd6 --- /dev/null +++ b/800OTClient/modules/game_skills/skills.otmod @@ -0,0 +1,11 @@ +Module + name: game_skills + description: Manage skills window + author: baxnie, edubart + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ skills ] + @onLoad: init() + @onUnload: terminate() + dependencies: + - game_interface diff --git a/800OTClient/modules/game_skills/skills.otui b/800OTClient/modules/game_skills/skills.otui new file mode 100644 index 0000000..c51e893 --- /dev/null +++ b/800OTClient/modules/game_skills/skills.otui @@ -0,0 +1,212 @@ +SkillFirstWidget < UIWidget + +SkillButton < UIButton + height: 21 + margin-bottom: 2 + &onClick: onSkillButtonClick + +SmallSkillButton < SkillButton + height: 14 + +SkillNameLabel < GameLabel + font: verdana-11px-monochrome + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + +SkillValueLabel < GameLabel + id: value + font: verdana-11px-monochrome + text-align: topright + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: prev.left + +SkillPercentPanel < ProgressBar + id: percent + background-color: green + height: 5 + margin-top: 15 + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + phantom: false + +MiniWindow + id: skillWindow + !text: tr('Skills') + height: 150 + icon: /images/topbuttons/skills + @onClose: modules.game_skills.onMiniWindowClose() + &save: true + &autoOpen: false + + MiniWindowContents + padding-left: 5 + padding-right: 5 + layout: verticalBox + + SkillButton + margin-top: 5 + id: experience + height: 15 + SkillNameLabel + !text: tr('Experience') + SkillValueLabel + + SkillButton + id: level + SkillNameLabel + !text: tr('Level') + SkillValueLabel + SkillPercentPanel + background-color: red + + SkillButton + id: health + height: 15 + SkillNameLabel + !text: tr('Hit Points') + SkillValueLabel + + SkillButton + id: mana + height: 15 + SkillNameLabel + !text: tr('Mana') + SkillValueLabel + + SkillButton + id: soul + height: 15 + SkillNameLabel + !text: tr('Soul Points') + SkillValueLabel + + SkillButton + id: capacity + height: 15 + SkillNameLabel + !text: tr('Capacity') + SkillValueLabel + + SkillButton + id: speed + height: 15 + SkillNameLabel + !text: tr('Speed') + SkillValueLabel + + SkillButton + id: regenerationTime + SkillNameLabel + !text: tr('Regeneration Time') + SkillValueLabel + + SkillButton + id: stamina + SkillNameLabel + !text: tr('Stamina') + SkillValueLabel + SkillPercentPanel + + SkillButton + id: offlineTraining + SkillNameLabel + !text: tr('Offline Training') + SkillValueLabel + SkillPercentPanel + + SkillButton + id: magiclevel + SkillNameLabel + !text: tr('Magic Level') + SkillValueLabel + SkillPercentPanel + background-color: red + + SkillButton + id: skillId0 + SkillNameLabel + !text: tr('Fist Fighting') + SkillValueLabel + SkillPercentPanel + + SkillButton + id: skillId1 + SkillNameLabel + !text: tr('Club Fighting') + SkillValueLabel + SkillPercentPanel + + SkillButton + id: skillId2 + SkillNameLabel + !text: tr('Sword Fighting') + SkillValueLabel + SkillPercentPanel + + SkillButton + id: skillId3 + SkillNameLabel + !text: tr('Axe Fighting') + SkillValueLabel + SkillPercentPanel + + SkillButton + id: skillId4 + SkillNameLabel + !text: tr('Distance Fighting') + SkillValueLabel + SkillPercentPanel + + SkillButton + id: skillId5 + SkillNameLabel + !text: tr('Shielding') + SkillValueLabel + SkillPercentPanel + + SkillButton + id: skillId6 + SkillNameLabel + !text: tr('Fishing') + SkillValueLabel + SkillPercentPanel + + SmallSkillButton + id: skillId7 + SkillNameLabel + !text: tr('Critical Hit Chance') + SkillValueLabel + + SmallSkillButton + id: skillId8 + SkillNameLabel + !text: tr('Critical Hit Damage') + SkillValueLabel + + SmallSkillButton + id: skillId9 + SkillNameLabel + !text: tr('Life Leech Chance') + SkillValueLabel + + SmallSkillButton + id: skillId10 + SkillNameLabel + !text: tr('Life Leech Amount') + SkillValueLabel + + SmallSkillButton + id: skillId11 + SkillNameLabel + !text: tr('Mana Leech Chance') + SkillValueLabel + + SmallSkillButton + id: skillId12 + SkillNameLabel + !text: tr('Mana Leech Amount') + SkillValueLabel diff --git a/800OTClient/modules/game_spelllist/spelllist.lua b/800OTClient/modules/game_spelllist/spelllist.lua new file mode 100644 index 0000000..367f675 --- /dev/null +++ b/800OTClient/modules/game_spelllist/spelllist.lua @@ -0,0 +1,390 @@ +local SpelllistProfile = 'Default' + +spelllistWindow = nil +spelllistButton = nil +spellList = nil +nameValueLabel = nil +formulaValueLabel = nil +vocationValueLabel = nil +groupValueLabel = nil +typeValueLabel = nil +cooldownValueLabel = nil +levelValueLabel = nil +manaValueLabel = nil +premiumValueLabel = nil +descriptionValueLabel = nil + +vocationBoxAny = nil +vocationBoxSorcerer = nil +vocationBoxDruid = nil +vocationBoxPaladin = nil +vocationBoxKnight = nil + +groupBoxAny = nil +groupBoxAttack = nil +groupBoxHealing = nil +groupBoxSupport = nil + +premiumBoxAny = nil +premiumBoxNo = nil +premiumBoxYes = nil + +vocationRadioGroup = nil +groupRadioGroup = nil +premiumRadioGroup = nil + +-- consts +FILTER_PREMIUM_ANY = 0 +FILTER_PREMIUM_NO = 1 +FILTER_PREMIUM_YES = 2 + +FILTER_VOCATION_ANY = 0 +FILTER_VOCATION_SORCERER = 1 +FILTER_VOCATION_DRUID = 2 +FILTER_VOCATION_PALADIN = 3 +FILTER_VOCATION_KNIGHT = 4 + +FILTER_GROUP_ANY = 0 +FILTER_GROUP_ATTACK = 1 +FILTER_GROUP_HEALING = 2 +FILTER_GROUP_SUPPORT = 3 + +-- Filter Settings +local filters = { + level = false, + vocation = false, + + vocationId = FILTER_VOCATION_ANY, + premium = FILTER_PREMIUM_ANY, + groupId = FILTER_GROUP_ANY +} + +function getSpelllistProfile() + return SpelllistProfile +end + +function setSpelllistProfile(name) + if SpelllistProfile == name then return end + + if SpelllistSettings[name] and SpellInfo[name] then + local oldProfile = SpelllistProfile + SpelllistProfile = name + changeSpelllistProfile(oldProfile) + else + perror('Spelllist profile \'' .. name .. '\' could not be set.') + end +end + +function online() + if g_game.getFeature(GameSpellList) then + spelllistButton:show() + else + spelllistButton:hide() + end + + -- Vocation is only send in newer clients + if g_game.getClientVersion() >= 950 then + spelllistWindow:getChildById('buttonFilterVocation'):setVisible(true) + else + spelllistWindow:getChildById('buttonFilterVocation'):setVisible(false) + end +end + +function offline() + resetWindow() +end + +function init() + connect(g_game, { onGameStart = online, + onGameEnd = offline }) + + spelllistWindow = g_ui.displayUI('spelllist', modules.game_interface.getRightPanel()) + spelllistWindow:hide() + + spelllistButton = modules.client_topmenu.addRightGameToggleButton('spelllistButton', tr('Spell List'), '/images/topbuttons/spelllist', toggle, false, 4) + spelllistButton:setOn(false) + + nameValueLabel = spelllistWindow:getChildById('labelNameValue') + formulaValueLabel = spelllistWindow:getChildById('labelFormulaValue') + vocationValueLabel = spelllistWindow:getChildById('labelVocationValue') + groupValueLabel = spelllistWindow:getChildById('labelGroupValue') + typeValueLabel = spelllistWindow:getChildById('labelTypeValue') + cooldownValueLabel = spelllistWindow:getChildById('labelCooldownValue') + levelValueLabel = spelllistWindow:getChildById('labelLevelValue') + manaValueLabel = spelllistWindow:getChildById('labelManaValue') + premiumValueLabel = spelllistWindow:getChildById('labelPremiumValue') + descriptionValueLabel = spelllistWindow:getChildById('labelDescriptionValue') + + vocationBoxAny = spelllistWindow:getChildById('vocationBoxAny') + vocationBoxSorcerer = spelllistWindow:getChildById('vocationBoxSorcerer') + vocationBoxDruid = spelllistWindow:getChildById('vocationBoxDruid') + vocationBoxPaladin = spelllistWindow:getChildById('vocationBoxPaladin') + vocationBoxKnight = spelllistWindow:getChildById('vocationBoxKnight') + + groupBoxAny = spelllistWindow:getChildById('groupBoxAny') + groupBoxAttack = spelllistWindow:getChildById('groupBoxAttack') + groupBoxHealing = spelllistWindow:getChildById('groupBoxHealing') + groupBoxSupport = spelllistWindow:getChildById('groupBoxSupport') + + premiumBoxAny = spelllistWindow:getChildById('premiumBoxAny') + premiumBoxYes = spelllistWindow:getChildById('premiumBoxYes') + premiumBoxNo = spelllistWindow:getChildById('premiumBoxNo') + + vocationRadioGroup = UIRadioGroup.create() + vocationRadioGroup:addWidget(vocationBoxAny) + vocationRadioGroup:addWidget(vocationBoxSorcerer) + vocationRadioGroup:addWidget(vocationBoxDruid) + vocationRadioGroup:addWidget(vocationBoxPaladin) + vocationRadioGroup:addWidget(vocationBoxKnight) + + groupRadioGroup = UIRadioGroup.create() + groupRadioGroup:addWidget(groupBoxAny) + groupRadioGroup:addWidget(groupBoxAttack) + groupRadioGroup:addWidget(groupBoxHealing) + groupRadioGroup:addWidget(groupBoxSupport) + + premiumRadioGroup = UIRadioGroup.create() + premiumRadioGroup:addWidget(premiumBoxAny) + premiumRadioGroup:addWidget(premiumBoxYes) + premiumRadioGroup:addWidget(premiumBoxNo) + + premiumRadioGroup:selectWidget(premiumBoxAny) + vocationRadioGroup:selectWidget(vocationBoxAny) + groupRadioGroup:selectWidget(groupBoxAny) + + vocationRadioGroup.onSelectionChange = toggleFilter + groupRadioGroup.onSelectionChange = toggleFilter + premiumRadioGroup.onSelectionChange = toggleFilter + + spellList = spelllistWindow:getChildById('spellList') + + g_keyboard.bindKeyPress('Down', function() spellList:focusNextChild(KeyboardFocusReason) end, spelllistWindow) + g_keyboard.bindKeyPress('Up', function() spellList:focusPreviousChild(KeyboardFocusReason) end, spelllistWindow) + + initializeSpelllist() + resizeWindow() + + if g_game.isOnline() then + online() + end +end + +function terminate() + disconnect(g_game, { onGameStart = online, + onGameEnd = offline }) + + disconnect(spellList, { onChildFocusChange = function(self, focusedChild) + if focusedChild == nil then return end + updateSpellInformation(focusedChild) + end }) + + spelllistWindow:destroy() + spelllistButton:destroy() + + vocationRadioGroup:destroy() + groupRadioGroup:destroy() + premiumRadioGroup:destroy() +end + +function initializeSpelllist() + for i = 1, #SpelllistSettings[SpelllistProfile].spellOrder do + local spell = SpelllistSettings[SpelllistProfile].spellOrder[i] + local info = SpellInfo[SpelllistProfile][spell] + + local tmpLabel = g_ui.createWidget('SpellListLabel', spellList) + tmpLabel:setId(spell) + tmpLabel:setText(spell .. '\n\'' .. info.words .. '\'') + tmpLabel:setPhantom(false) + + local iconId = tonumber(info.icon) + if not iconId and SpellIcons[info.icon] then + iconId = SpellIcons[info.icon][1] + end + + if not(iconId) then + perror('Spell icon \'' .. info.icon .. '\' not found.') + end + + tmpLabel:setHeight(SpelllistSettings[SpelllistProfile].iconSize.height + 4) + tmpLabel:setTextOffset(topoint((SpelllistSettings[SpelllistProfile].iconSize.width + 10) .. ' ' .. (SpelllistSettings[SpelllistProfile].iconSize.height - 32)/2 + 3)) + tmpLabel:setImageSource(SpelllistSettings[SpelllistProfile].iconFile) + tmpLabel:setImageClip(Spells.getImageClip(iconId, SpelllistProfile)) + tmpLabel:setImageSize(tosize(SpelllistSettings[SpelllistProfile].iconSize.width .. ' ' .. SpelllistSettings[SpelllistProfile].iconSize.height)) + tmpLabel.onClick = updateSpellInformation + end + + connect(spellList, { onChildFocusChange = function(self, focusedChild) + if focusedChild == nil then return end + updateSpellInformation(focusedChild) + end }) +end + +function changeSpelllistProfile(oldProfile) + -- Delete old labels + for i = 1, #SpelllistSettings[oldProfile].spellOrder do + local spell = SpelllistSettings[oldProfile].spellOrder[i] + local tmpLabel = spellList:getChildById(spell) + + tmpLabel:destroy() + end + + -- Create new spelllist and ajust window + initializeSpelllist() + resizeWindow() + resetWindow() +end + +function updateSpelllist() + for i = 1, #SpelllistSettings[SpelllistProfile].spellOrder do + local spell = SpelllistSettings[SpelllistProfile].spellOrder[i] + local info = SpellInfo[SpelllistProfile][spell] + local tmpLabel = spellList:getChildById(spell) + + local localPlayer = g_game.getLocalPlayer() + if (not(filters.level) or info.level <= localPlayer:getLevel()) and (not(filters.vocation) or table.find(info.vocations, localPlayer:getVocation())) and (filters.vocationId == FILTER_VOCATION_ANY or table.find(info.vocations, filters.vocationId) or table.find(info.vocations, filters.vocationId+4)) and (filters.groupId == FILTER_GROUP_ANY or info.group[filters.groupId]) and (filters.premium == FILTER_PREMIUM_ANY or (info.premium and filters.premium == FILTER_PREMIUM_YES) or (not(info.premium) and filters.premium == FILTER_PREMIUM_NO)) then + tmpLabel:setVisible(true) + else + tmpLabel:setVisible(false) + end + end +end + +function updateSpellInformation(widget) + local spell = widget:getId() + + local name = '' + local formula = '' + local vocation = '' + local group = '' + local type = '' + local cooldown = '' + local level = '' + local mana = '' + local premium = '' + local description = '' + + if SpellInfo[SpelllistProfile][spell] then + local info = SpellInfo[SpelllistProfile][spell] + + name = spell + formula = info.words + + for i = 1, #info.vocations do + local vocationId = info.vocations[i] + if vocationId <= 4 or not(table.find(info.vocations, (vocationId-4))) then + vocation = vocation .. (vocation:len() == 0 and '' or ', ') .. VocationNames[vocationId] + end + end + + cooldown = (info.exhaustion / 1000) .. 's' + for groupId, groupName in ipairs(SpellGroups) do + if info.group[groupId] then + group = group .. (group:len() == 0 and '' or ' / ') .. groupName + cooldown = cooldown .. ' / ' .. (info.group[groupId] / 1000) .. 's' + end + end + + type = info.type + level = info.level + mana = info.mana .. ' / ' .. info.soul + premium = (info.premium and 'yes' or 'no') + description = info.description or '-' + end + + nameValueLabel:setText(name) + formulaValueLabel:setText(formula) + vocationValueLabel:setText(vocation) + groupValueLabel:setText(group) + typeValueLabel:setText(type) + cooldownValueLabel:setText(cooldown) + levelValueLabel:setText(level) + manaValueLabel:setText(mana) + premiumValueLabel:setText(premium) + descriptionValueLabel:setText(description) +end + +function toggle() + if spelllistButton:isOn() then + spelllistButton:setOn(false) + spelllistWindow:hide() + else + spelllistButton:setOn(true) + spelllistWindow:show() + spelllistWindow:raise() + spelllistWindow:focus() + end +end + +function toggleFilter(widget, selectedWidget) + if widget == vocationRadioGroup then + local boxId = selectedWidget:getId() + if boxId == 'vocationBoxAny' then + filters.vocationId = FILTER_VOCATION_ANY + elseif boxId == 'vocationBoxSorcerer' then + filters.vocationId = FILTER_VOCATION_SORCERER + elseif boxId == 'vocationBoxDruid' then + filters.vocationId = FILTER_VOCATION_DRUID + elseif boxId == 'vocationBoxPaladin' then + filters.vocationId = FILTER_VOCATION_PALADIN + elseif boxId == 'vocationBoxKnight' then + filters.vocationId = FILTER_VOCATION_KNIGHT + end + elseif widget == groupRadioGroup then + local boxId = selectedWidget:getId() + if boxId == 'groupBoxAny' then + filters.groupId = FILTER_GROUP_ANY + elseif boxId == 'groupBoxAttack' then + filters.groupId = FILTER_GROUP_ATTACK + elseif boxId == 'groupBoxHealing' then + filters.groupId = FILTER_GROUP_HEALING + elseif boxId == 'groupBoxSupport' then + filters.groupId = FILTER_GROUP_SUPPORT + end + elseif widget == premiumRadioGroup then + local boxId = selectedWidget:getId() + if boxId == 'premiumBoxAny' then + filters.premium = FILTER_PREMIUM_ANY + elseif boxId == 'premiumBoxNo' then + filters.premium = FILTER_PREMIUM_NO + elseif boxId == 'premiumBoxYes' then + filters.premium = FILTER_PREMIUM_YES + end + else + local id = widget:getId() + if id == 'buttonFilterLevel' then + filters.level = not(filters.level) + widget:setOn(filters.level) + elseif id == 'buttonFilterVocation' then + filters.vocation = not(filters.vocation) + widget:setOn(filters.vocation) + end + end + + updateSpelllist() +end + +function resizeWindow() + spelllistWindow:setWidth(SpelllistSettings['Default'].spellWindowWidth + SpelllistSettings[SpelllistProfile].iconSize.width - 32) + spellList:setWidth(SpelllistSettings['Default'].spellListWidth + SpelllistSettings[SpelllistProfile].iconSize.width - 32) +end + +function resetWindow() + spelllistWindow:hide() + spelllistButton:setOn(false) + + -- Resetting filters + filters.level = false + filters.vocation = false + + local buttonFilterLevel = spelllistWindow:getChildById('buttonFilterLevel') + buttonFilterLevel:setOn(filters.level) + + local buttonFilterVocation = spelllistWindow:getChildById('buttonFilterVocation') + buttonFilterVocation:setOn(filters.vocation) + + vocationRadioGroup:selectWidget(vocationBoxAny) + groupRadioGroup:selectWidget(groupBoxAny) + premiumRadioGroup:selectWidget(premiumBoxAny) + + updateSpelllist() +end diff --git a/800OTClient/modules/game_spelllist/spelllist.otmod b/800OTClient/modules/game_spelllist/spelllist.otmod new file mode 100644 index 0000000..4e3eaec --- /dev/null +++ b/800OTClient/modules/game_spelllist/spelllist.otmod @@ -0,0 +1,9 @@ +Module + name: game_spelllist + description: View available spells + author: Summ, Edubart + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ spelllist ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_spelllist/spelllist.otui b/800OTClient/modules/game_spelllist/spelllist.otui new file mode 100644 index 0000000..7bbc46b --- /dev/null +++ b/800OTClient/modules/game_spelllist/spelllist.otui @@ -0,0 +1,326 @@ +SpellListLabel < Label + font: verdana-11px-monochrome + background-color: alpha + text-offset: 42 3 + focusable: true + height: 36 + image-clip: 0 0 32 32 + image-size: 32 32 + image-offset: 2 2 + image-source: /images/game/spells/defaultspells + + $focus: + background-color: #ffffff22 + color: #ffffff + +SpellInfoLabel < Label + width: 70 + font: verdana-11px-monochrome + text-align: right + margin-left: 10 + margin-top: 5 + +SpellInfoValueLabel < Label + text-align: left + width: 190 + margin-left: 10 + margin-top: 5 + +FilterButton < Button + width: 64 + anchors.left: prev.right + anchors.top: spellList.bottom + @onClick: toggleFilter(self) + margin: 5 0 0 6 + color: #630000 + $on: + color: green + +MainWindow + id: spelllistWindow + !text: tr('Spell List') + size: 550 400 + @onEscape: toggle() + + TextList + id: spellList + vertical-scrollbar: spellsScrollBar + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: next.top + margin-bottom: 10 + padding: 1 + width: 210 + focusable: false + + Button + id: buttonCancel + !text: tr('Close') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: toggle() + + VerticalScrollBar + id: spellsScrollBar + anchors.top: spellList.top + anchors.bottom: spellList.bottom + anchors.right: spellList.right + step: 50 + pixels-scroll: true + + SpellInfoLabel + id: labelName + anchors.left: spellList.right + anchors.top: spellList.top + !text: tr('Name') .. ':' + + Label + anchors.left: parent.left + anchors.top: spellList.bottom + !text: tr('Filters') .. ':' + margin-top: 8 + + FilterButton + id: buttonFilterLevel + !text: tr('Level') + !tooltip: tr('Hide spells for higher exp. levels') + + FilterButton + id: buttonFilterVocation + !text: tr('Vocation') + !tooltip: tr('Hide spells for other vocations') + + SpellInfoLabel + id: labelFormula + anchors.left: spellList.right + anchors.top: labelName.bottom + !text: tr('Formula') .. ':' + + + SpellInfoLabel + id: labelVocation + anchors.left: spellList.right + anchors.top: labelFormula.bottom + !text: tr('Vocation') .. ':' + + SpellInfoLabel + id: labelGroup + anchors.left: spellList.right + anchors.top: labelVocation.bottom + !text: tr('Group') .. ':' + + SpellInfoLabel + id: labelType + anchors.left: spellList.right + anchors.top: labelGroup.bottom + !text: tr('Type') .. ':' + + SpellInfoLabel + id: labelCooldown + anchors.left: spellList.right + anchors.top: labelType.bottom + !text: tr('Cooldown') .. ':' + + SpellInfoLabel + id: labelLevel + anchors.left: spellList.right + anchors.top: labelCooldown.bottom + !text: tr('Level') .. ':' + + SpellInfoLabel + id: labelMana + anchors.left: spellList.right + anchors.top: labelLevel.bottom + !text: tr('Mana') .. ' / ' .. tr('Soul') .. ':' + + SpellInfoLabel + id: labelPremium + anchors.left: spellList.right + anchors.top: labelMana.bottom + !text: tr('Premium') .. ':' + + SpellInfoLabel + id: labelDescription + anchors.left: spellList.right + anchors.top: labelPremium.bottom + !text: tr('Description') .. ':' + + SpellInfoValueLabel + id: labelNameValue + anchors.left: labelName.right + anchors.top: spellList.top + + SpellInfoValueLabel + id: labelFormulaValue + anchors.left: labelFormula.right + anchors.top: labelNameValue.bottom + + SpellInfoValueLabel + id: labelVocationValue + anchors.left: labelVocation.right + anchors.top: labelFormulaValue.bottom + + SpellInfoValueLabel + id: labelGroupValue + anchors.left: labelGroup.right + anchors.top: labelVocationValue.bottom + + SpellInfoValueLabel + id: labelTypeValue + anchors.left: labelType.right + anchors.top: labelGroupValue.bottom + + SpellInfoValueLabel + id: labelCooldownValue + anchors.left: labelCooldown.right + anchors.top: labelTypeValue.bottom + + SpellInfoValueLabel + id: labelLevelValue + anchors.left: labelLevel.right + anchors.top: labelCooldownValue.bottom + + SpellInfoValueLabel + id: labelManaValue + anchors.left: labelMana.right + anchors.top: labelLevelValue.bottom + + SpellInfoValueLabel + id: labelPremiumValue + anchors.left: labelPremium.right + anchors.top: labelManaValue.bottom + + SpellInfoValueLabel + id: labelDescriptionValue + anchors.left: labelDescription.right + anchors.top: labelPremiumValue.bottom + + Label + id: labelVocationFilter + anchors.top: labelPremium.bottom + anchors.left: spellList.right + width: 70 + font: verdana-11px-monochrome + !text: tr('Vocation') + margin-top: 30 + margin-left: 20 + + CheckBox + id: vocationBoxAny + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + margin-left: 3 + !text: tr('Any') + width: 75 + + CheckBox + id: vocationBoxSorcerer + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + !text: tr('Sorcerer') + width: 75 + + CheckBox + id: vocationBoxDruid + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + !text: tr('Druid') + width: 75 + + CheckBox + id: vocationBoxPaladin + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + !text: tr('Paladin') + width: 75 + + CheckBox + id: vocationBoxKnight + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + !text: tr('Knight') + width: 75 + + Label + id: labelGroupFilter + anchors.top: labelPremium.bottom + anchors.left: labelVocationFilter.right + width: 70 + font: verdana-11px-monochrome + !text: tr('Group') + margin-top: 30 + margin-left: 20 + + CheckBox + id: groupBoxAny + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + margin-left: 3 + !text: tr('Any') + width: 75 + + CheckBox + id: groupBoxAttack + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + !text: tr('Attack') + width: 75 + + CheckBox + id: groupBoxHealing + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + !text: tr('Healing') + width: 75 + + CheckBox + id: groupBoxSupport + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + !text: tr('Support') + width: 75 + + Label + id: labelPremiumFilter + anchors.top: labelPremium.bottom + anchors.left: labelGroupFilter.right + width: 70 + font: verdana-11px-monochrome + !text: tr('Premium') + margin-top: 30 + margin-left: 20 + + CheckBox + id: premiumBoxAny + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + margin-left: 3 + !text: tr('Any') + width: 75 + + CheckBox + id: premiumBoxNo + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + !text: tr('No') + width: 75 + + CheckBox + id: premiumBoxYes + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + !text: tr('Yes') + width: 75 diff --git a/800OTClient/modules/game_stats/stats.lua b/800OTClient/modules/game_stats/stats.lua new file mode 100644 index 0000000..df5912c --- /dev/null +++ b/800OTClient/modules/game_stats/stats.lua @@ -0,0 +1,58 @@ +ui = nil +updateEvent = nil + +function init() + ui = g_ui.loadUI('stats', modules.game_interface.getMapPanel()) + + if not modules.client_options.getOption("showPing") then + ui.fps:hide() + end + if not modules.client_options.getOption("showFps") then + ui.ping:hide() + end + + updateEvent = scheduleEvent(update, 200) +end + +function terminate() + removeEvent(updateEvent) +end + +function update() + updateEvent = scheduleEvent(update, 500) + if ui:isHidden() then return end + + text = 'FPS: ' .. g_app.getFps() + ui.fps:setText(text) + + local ping = g_game.getPing() + if g_proxy and g_proxy.getPing() > 0 then + ping = g_proxy.getPing() + end + + local text = 'Ping: ' + local color + if ping < 0 then + text = text .. "??" + color = 'yellow' + else + text = text .. ping .. ' ms' + if ping >= 500 then + color = 'red' + elseif ping >= 250 then + color = 'yellow' + else + color = 'green' + end + end + ui.ping:setText(text) + ui.ping:setColor(color) +end + +function show() + ui:setVisible(true) +end + +function hide() + ui:setVisible(false) +end \ No newline at end of file diff --git a/800OTClient/modules/game_stats/stats.otmod b/800OTClient/modules/game_stats/stats.otmod new file mode 100644 index 0000000..fc6a6c8 --- /dev/null +++ b/800OTClient/modules/game_stats/stats.otmod @@ -0,0 +1,9 @@ +Module + name: game_stats + description: Display ping and fps + author: otclient.ovh + website: http://otclient.ovh + sandboxed: true + scripts: [ stats ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_stats/stats.otui b/800OTClient/modules/game_stats/stats.otui new file mode 100644 index 0000000..33fcfb4 --- /dev/null +++ b/800OTClient/modules/game_stats/stats.otui @@ -0,0 +1,17 @@ +UIWidget + id: game_stats + anchors.top: parent.top + anchors.left: parent.left + margin-left: 3 + size: 100 100 + visible: false + layout: + type: verticalBox + + Label + id: fps + font: verdana-11px-rounded + + Label + id: ping + font: verdana-11px-rounded diff --git a/800OTClient/modules/game_textmessage/textmessage.lua b/800OTClient/modules/game_textmessage/textmessage.lua new file mode 100644 index 0000000..47f9fdf --- /dev/null +++ b/800OTClient/modules/game_textmessage/textmessage.lua @@ -0,0 +1,135 @@ +MessageSettings = { + none = {}, + consoleRed = { color = TextColors.red, consoleTab='Default' }, + consoleOrange = { color = TextColors.orange, consoleTab='Default' }, + consoleBlue = { color = TextColors.blue, consoleTab='Default' }, + centerRed = { color = TextColors.red, consoleTab='Server Log', screenTarget='lowCenterLabel' }, + centerGreen = { color = TextColors.green, consoleTab='Server Log', screenTarget='highCenterLabel', consoleOption='showInfoMessagesInConsole' }, + centerWhite = { color = TextColors.white, consoleTab='Server Log', screenTarget='middleCenterLabel', consoleOption='showEventMessagesInConsole' }, + bottomWhite = { color = TextColors.white, consoleTab='Server Log', screenTarget='statusLabel', consoleOption='showEventMessagesInConsole' }, + status = { color = TextColors.white, consoleTab='Server Log', screenTarget='statusLabel', consoleOption='showStatusMessagesInConsole' }, + statusSmall = { color = TextColors.white, screenTarget='statusLabel' }, + private = { color = TextColors.lightblue, screenTarget='privateLabel' } +} + +MessageTypes = { + [MessageModes.MonsterSay] = MessageSettings.consoleOrange, + [MessageModes.MonsterYell] = MessageSettings.consoleOrange, + [MessageModes.BarkLow] = MessageSettings.consoleOrange, + [MessageModes.BarkLoud] = MessageSettings.consoleOrange, + [MessageModes.Failure] = MessageSettings.statusSmall, + [MessageModes.Login] = MessageSettings.bottomWhite, + [MessageModes.Game] = MessageSettings.centerWhite, + [MessageModes.Status] = MessageSettings.status, + [MessageModes.Warning] = MessageSettings.centerRed, + [MessageModes.Look] = MessageSettings.centerGreen, + [MessageModes.Loot] = MessageSettings.centerGreen, + [MessageModes.Red] = MessageSettings.consoleRed, + [MessageModes.Blue] = MessageSettings.consoleBlue, + [MessageModes.PrivateFrom] = MessageSettings.consoleBlue, + + [MessageModes.GamemasterBroadcast] = MessageSettings.consoleRed, + + [MessageModes.DamageDealed] = MessageSettings.status, + [MessageModes.DamageReceived] = MessageSettings.status, + [MessageModes.Heal] = MessageSettings.status, + [MessageModes.Exp] = MessageSettings.status, + + [MessageModes.DamageOthers] = MessageSettings.none, + [MessageModes.HealOthers] = MessageSettings.none, + [MessageModes.ExpOthers] = MessageSettings.none, + + [MessageModes.TradeNpc] = MessageSettings.centerWhite, + [MessageModes.Guild] = MessageSettings.centerWhite, + [MessageModes.Party] = MessageSettings.centerGreen, + [MessageModes.PartyManagement] = MessageSettings.centerWhite, + [MessageModes.TutorialHint] = MessageSettings.centerWhite, + [MessageModes.BeyondLast] = MessageSettings.centerWhite, + [MessageModes.Report] = MessageSettings.consoleRed, + [MessageModes.HotkeyUse] = MessageSettings.centerGreen, + + [254] = MessageSettings.private +} + +messagesPanel = nil + +function init() + for messageMode, _ in pairs(MessageTypes) do + registerMessageMode(messageMode, displayMessage) + end + + connect(g_game, 'onGameEnd', clearMessages) + messagesPanel = g_ui.loadUI('textmessage', modules.game_interface.getRootPanel()) +end + +function terminate() + for messageMode, _ in pairs(MessageTypes) do + unregisterMessageMode(messageMode, displayMessage) + end + + disconnect(g_game, 'onGameEnd', clearMessages) + clearMessages() + messagesPanel:destroy() +end + +function calculateVisibleTime(text) + return math.max(#text * 50, 3000) +end + +function displayMessage(mode, text) + if not g_game.isOnline() then return end + + local msgtype = MessageTypes[mode] + if not msgtype then + return + end + + if msgtype == MessageSettings.none then return end + + if msgtype.consoleTab ~= nil and (msgtype.consoleOption == nil or modules.client_options.getOption(msgtype.consoleOption)) then + modules.game_console.addText(text, msgtype, tr(msgtype.consoleTab)) + --TODO move to game_console + end + + if msgtype.screenTarget then + local label = messagesPanel:recursiveGetChildById(msgtype.screenTarget) + label:setText(text) + label:setColor(msgtype.color) + label:setVisible(true) + removeEvent(label.hideEvent) + label.hideEvent = scheduleEvent(function() label:setVisible(false) end, calculateVisibleTime(text)) + end +end + +function displayPrivateMessage(text) + displayMessage(254, text) +end + +function displayStatusMessage(text) + displayMessage(MessageModes.Status, text) +end + +function displayFailureMessage(text) + displayMessage(MessageModes.Failure, text) +end + +function displayGameMessage(text) + displayMessage(MessageModes.Game, text) +end + +function displayBroadcastMessage(text) + displayMessage(MessageModes.Warning, text) +end + +function clearMessages() + for _i,child in pairs(messagesPanel:recursiveGetChildren()) do + if child:getId():match('Label') then + child:hide() + removeEvent(child.hideEvent) + end + end +end + +function LocalPlayer:onAutoWalkFail(player) + modules.game_textmessage.displayFailureMessage(tr('There is no way.')) +end diff --git a/800OTClient/modules/game_textmessage/textmessage.otmod b/800OTClient/modules/game_textmessage/textmessage.otmod new file mode 100644 index 0000000..068d066 --- /dev/null +++ b/800OTClient/modules/game_textmessage/textmessage.otmod @@ -0,0 +1,9 @@ +Module + name: game_textmessage + description: Manage game text messages + author: edubart + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ textmessage ] + @onLoad: init() + @onUnload: terminate() \ No newline at end of file diff --git a/800OTClient/modules/game_textmessage/textmessage.otui b/800OTClient/modules/game_textmessage/textmessage.otui new file mode 100644 index 0000000..7af5ed1 --- /dev/null +++ b/800OTClient/modules/game_textmessage/textmessage.otui @@ -0,0 +1,40 @@ +TextMessageLabel < UILabel + font: verdana-11px-rounded + text-align: center + text-wrap: true + text-auto-resize: true + margin-bottom: 2 + visible: false + +Panel + anchors.fill: gameMapPanel + focusable: false + + Panel + id: centerTextMessagePanel + layout: + type: verticalBox + fit-children: true + width: 360 + anchors.centerIn: parent + + TextMessageLabel + id: highCenterLabel + TextMessageLabel + id: middleCenterLabel + TextMessageLabel + id: lowCenterLabel + + TextMessageLabel + id: privateLabel + anchors.top: parent.top + anchors.bottom: centerTextMessagePanel.top + anchors.horizontalCenter: parent.horizontalCenter + text-auto-resize: false + width: 275 + + TextMessageLabel + id: statusLabel + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right diff --git a/800OTClient/modules/game_textwindow/textwindow.lua b/800OTClient/modules/game_textwindow/textwindow.lua new file mode 100644 index 0000000..e4e4040 --- /dev/null +++ b/800OTClient/modules/game_textwindow/textwindow.lua @@ -0,0 +1,144 @@ +windows = {} + +function init() + g_ui.importStyle('textwindow') + + connect(g_game, { onEditText = onGameEditText, + onEditList = onGameEditList, + onGameEnd = destroyWindows }) +end + +function terminate() + disconnect(g_game, { onEditText = onGameEditText, + onEditList = onGameEditList, + onGameEnd = destroyWindows }) + + destroyWindows() +end + +function destroyWindows() + for _,window in pairs(windows) do + window:destroy() + end + windows = {} +end + +function onGameEditText(id, itemId, maxLength, text, writer, time) + local textWindow = g_ui.createWidget('TextWindow', rootWidget) + + local writeable = #text < maxLength and maxLength > 0 + local textItem = textWindow:getChildById('textItem') + local description = textWindow:getChildById('description') + local textEdit = textWindow:getChildById('text') + local okButton = textWindow:getChildById('okButton') + local cancelButton = textWindow:getChildById('cancelButton') + + local textScroll = textWindow:getChildById('textScroll') + + if textItem:isHidden() then + textItem:show() + end + + textItem:setItemId(itemId) + textEdit:setMaxLength(maxLength) + textEdit:setText(text) + textEdit:setEditable(writeable) + textEdit:setCursorVisible(writeable) + + local desc = '' + if #writer > 0 then + desc = tr('You read the following, written by \n%s\n', writer) + if #time > 0 then + desc = desc .. tr('on %s.\n', time) + end + elseif #time > 0 then + desc = tr('You read the following, written on \n%s.\n', time) + end + + if #text == 0 and not writeable then + desc = tr("It is empty.") + elseif writeable then + desc = desc .. tr('You can enter new text.') + end + + local lines = #{string.find(desc, '\n')} + if lines < 2 then desc = desc .. '\n' end + + description:setText(desc) + + if not writeable then + textWindow:setText(tr('Show Text')) + cancelButton:hide() + cancelButton:setWidth(0) + okButton:setMarginRight(0) + else + textWindow:setText(tr('Edit Text')) + end + + if description:getHeight() < 64 then + description:setHeight(64) + end + + local function destroy() + textWindow:destroy() + table.removevalue(windows, textWindow) + end + + local doneFunc = function() + if writeable then + g_game.editText(id, textEdit:getText()) + end + destroy() + end + + okButton.onClick = doneFunc + cancelButton.onClick = destroy + + if not writeable then + textWindow.onEnter = doneFunc + end + + textWindow.onEscape = destroy + + table.insert(windows, textWindow) +end + +function onGameEditList(id, doorId, text) + local textWindow = g_ui.createWidget('TextWindow', rootWidget) + + local textEdit = textWindow:getChildById('text') + local description = textWindow:getChildById('description') + local okButton = textWindow:getChildById('okButton') + local cancelButton = textWindow:getChildById('cancelButton') + + local textItem = textWindow:getChildById('textItem') + if textItem and not textItem:isHidden() then + textItem:hide() + end + + textEdit:setMaxLength(8192) + textEdit:setText(text) + textEdit:setEditable(true) + description:setText(tr('Enter one name per line.')) + textWindow:setText(tr('Edit List')) + + if description:getHeight() < 64 then + description:setHeight(64) + end + + local function destroy() + textWindow:destroy() + table.removevalue(windows, textWindow) + end + + local doneFunc = function() + g_game.editList(id, doorId, textEdit:getText()) + destroy() + end + + okButton.onClick = doneFunc + cancelButton.onClick = destroy + textWindow.onEscape = destroy + + table.insert(windows, textWindow) +end diff --git a/800OTClient/modules/game_textwindow/textwindow.otmod b/800OTClient/modules/game_textwindow/textwindow.otmod new file mode 100644 index 0000000..d70d650 --- /dev/null +++ b/800OTClient/modules/game_textwindow/textwindow.otmod @@ -0,0 +1,10 @@ +Module + name: game_textwindow + description: Allow to edit text books and lists + author: edubart, BeniS + website: https://github.com/edubart/otclient + sandboxed: true + dependencies: [ game_interface ] + scripts: [ textwindow ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_textwindow/textwindow.otui b/800OTClient/modules/game_textwindow/textwindow.otui new file mode 100644 index 0000000..d803737 --- /dev/null +++ b/800OTClient/modules/game_textwindow/textwindow.otui @@ -0,0 +1,53 @@ +TextWindow < MainWindow + id: textWindow + size: 300 280 + + Item + id: textItem + virtual: true + anchors.top: parent.top + anchors.left: parent.left + + Label + id: description + anchors.top: parent.top + anchors.left: textItem.right + anchors.right: parent.right + margin-left: 8 + text-auto-resize: true + text-align: left + text-wrap: true + + MultilineTextEdit + id: text + anchors.top: textScroll.top + anchors.left: parent.left + anchors.right: textScroll.left + anchors.bottom: textScroll.bottom + vertical-scrollbar: textScroll + text-wrap: true + + VerticalScrollBar + id: textScroll + anchors.top: description.bottom + anchors.bottom: okButton.top + anchors.right: parent.right + margin-top: 10 + margin-bottom: 10 + step: 16 + pixels-scroll: true + + Button + id: okButton + !text: tr('Ok') + anchors.bottom: parent.bottom + anchors.right: next.left + margin-right: 10 + width: 60 + + Button + id: cancelButton + !text: tr('Cancel') + anchors.bottom: parent.bottom + anchors.right: parent.right + width: 60 diff --git a/800OTClient/modules/game_things/things.lua b/800OTClient/modules/game_things/things.lua new file mode 100644 index 0000000..cfe5f88 --- /dev/null +++ b/800OTClient/modules/game_things/things.lua @@ -0,0 +1,54 @@ +filename = nil +loaded = false + +function setFileName(name) + filename = name +end + +function isLoaded() + return loaded +end + +function load() + local version = g_game.getClientVersion() + local things = g_settings.getNode('things') + + local datPath, sprPath + if things and things["data"] ~= nil and things["sprites"] ~= nil then + datPath = resolvepath('/things/' .. things["data"]) + sprPath = resolvepath('/things/' .. things["sprites"]) + else + if filename then + datPath = resolvepath('/things/' .. filename) + sprPath = resolvepath('/things/' .. filename) + else + datPath = resolvepath('/things/' .. version .. '/Tibia') + sprPath = resolvepath('/things/' .. version .. '/Tibia') + end + end + + local errorMessage = '' + if not g_things.loadDat(datPath) then + if not g_game.getFeature(GameSpritesU32) then + g_game.enableFeature(GameSpritesU32) + if not g_things.loadDat(datPath) then + errorMessage = errorMessage .. tr("Unable to load dat file, please place a valid dat in '%s'", datPath) .. '\n' + end + else + errorMessage = errorMessage .. tr("Unable to load dat file, please place a valid dat in '%s'", datPath) .. '\n' + end + end + if not g_sprites.loadSpr(sprPath) then + errorMessage = errorMessage .. tr("Unable to load spr file, please place a valid spr in '%s'", sprPath) + end + + loaded = (errorMessage:len() == 0) + + if errorMessage:len() > 0 then + local messageBox = displayErrorBox(tr('Error'), errorMessage) + addEvent(function() messageBox:raise() messageBox:focus() end) + + g_game.setClientVersion(0) + g_game.setProtocolVersion(0) + end +end diff --git a/800OTClient/modules/game_things/things.otmod b/800OTClient/modules/game_things/things.otmod new file mode 100644 index 0000000..c2536d3 --- /dev/null +++ b/800OTClient/modules/game_things/things.otmod @@ -0,0 +1,6 @@ +Module + name: game_things + description: Contains things spr and dat + reloadable: false + sandboxed: true + scripts: [things] diff --git a/800OTClient/modules/game_topbar/topbar.lua b/800OTClient/modules/game_topbar/topbar.lua new file mode 100644 index 0000000..da73010 --- /dev/null +++ b/800OTClient/modules/game_topbar/topbar.lua @@ -0,0 +1,504 @@ +Icons = {} +Icons[PlayerStates.Poison] = { + tooltip = tr('You are poisoned'), + path = '/images/game/states/poisoned', + id = 'condition_poisoned' +} +Icons[PlayerStates.Burn] = { + tooltip = tr('You are burning'), + path = '/images/game/states/burning', + id = 'condition_burning' +} +Icons[PlayerStates.Energy] = { + tooltip = tr('You are electrified'), + path = '/images/game/states/electrified', + id = 'condition_electrified' +} +Icons[PlayerStates.Drunk] = { + tooltip = tr('You are drunk'), + path = '/images/game/states/drunk', + id = 'condition_drunk' +} +Icons[PlayerStates.ManaShield] = { + tooltip = tr('You are protected by a magic shield'), + path = '/images/game/states/magic_shield', + id = 'condition_magic_shield' +} +Icons[PlayerStates.Paralyze] = { + tooltip = tr('You are paralysed'), + path = '/images/game/states/slowed', + id = 'condition_slowed' +} +Icons[PlayerStates.Haste] = { + tooltip = tr('You are hasted'), + path = '/images/game/states/haste', + id = 'condition_haste' +} +Icons[PlayerStates.Swords] = { + tooltip = tr('You may not logout during a fight'), + path = '/images/game/states/logout_block', + id = 'condition_logout_block' +} +Icons[PlayerStates.Drowning] = { + tooltip = tr('You are drowning'), + path = '/images/game/states/drowning', + id = 'condition_drowning' +} +Icons[PlayerStates.Freezing] = { + tooltip = tr('You are freezing'), + path = '/images/game/states/freezing', + id = 'condition_freezing' +} +Icons[PlayerStates.Dazzled] = { + tooltip = tr('You are dazzled'), + path = '/images/game/states/dazzled', + id = 'condition_dazzled' +} +Icons[PlayerStates.Cursed] = { + tooltip = tr('You are cursed'), + path = '/images/game/states/cursed', + id = 'condition_cursed' +} +Icons[PlayerStates.PartyBuff] = { + tooltip = tr('You are strengthened'), + path = '/images/game/states/strengthened', + id = 'condition_strengthened' +} +Icons[PlayerStates.PzBlock] = { + tooltip = tr('You may not logout or enter a protection zone'), + path = '/images/game/states/protection_zone_block', + id = 'condition_protection_zone_block' +} +Icons[PlayerStates.Pz] = { + tooltip = tr('You are within a protection zone'), + path = '/images/game/states/protection_zone', + id = 'condition_protection_zone' +} +Icons[PlayerStates.Bleeding] = { + tooltip = tr('You are bleeding'), + path = '/images/game/states/bleeding', + id = 'condition_bleeding' +} +Icons[PlayerStates.Hungry] = { + tooltip = tr('You are hungry'), + path = '/images/game/states/hungry', + id = 'condition_hungry' +} + +local iconsTable = { + ["Experience"] = 8, + ["Magic"] = 0, + ["Axe"] = 2, + ["Club"] = 1, + ["Distance"] = 3, + ["Fist"] = 4, + ["Shielding"] = 5, + ["Sword"] = 6, + ["Fishing"] = 7 +} + +local healthBar = nil +local manaBar = nil +local topBar = nil +local states = nil +local experienceTooltip = 'You have %d%% to advance to level %d.' +local settings = {} + +function init() + + connect(LocalPlayer, { + onHealthChange = onHealthChange, + onManaChange = onManaChange, + onLevelChange = onLevelChange, + onStatesChange = onStatesChange, + onMagicLevelChange = onMagicLevelChange, + onBaseMagicLevelChange = onBaseMagicLevelChange, + onSkillChange = onSkillChange, + onBaseSkillChange = onBaseSkillChange + }) + connect(g_game, {onGameStart = refresh, onGameEnd = offline}) + + -- load condition icons + for k, v in pairs(Icons) do g_textures.preload(v.path) end + + if g_game.isOnline() then refresh() end +end + +function terminate() + + disconnect(LocalPlayer, { + onHealthChange = onHealthChange, + onManaChange = onManaChange, + onLevelChange = onLevelChange, + onStatesChange = onStatesChange, + onMagicLevelChange = onMagicLevelChange, + onBaseMagicLevelChange = onBaseMagicLevelChange, + onSkillChange = onSkillChange, + onBaseSkillChange = onBaseSkillChange + }) + disconnect(g_game, {onGameStart = refresh, onGameEnd = offline}) +end + +function setupTopBar() + local topPanel = modules.game_interface.getTopBar() + topBar = topBar or g_ui.loadUI('TopBar', topPanel) + topBar = topBar or g_ui.createWidget('TopBar', topPanel) + + manaBar = topBar.stats.mana + healthBar = topBar.stats.health + states = topBar.stats.states.box + + topBar.onMouseRelease = function(widget, mousePos, mouseButton) + menu(mouseButton) + end +end + +function refresh(profileChange) + local player = g_game.getLocalPlayer() + if not player then return end + + setupTopBar() + load() + setupSkills() + show() + refreshVisibleBars() + + onLevelChange(player, player:getLevel(), player:getLevelPercent()) + onHealthChange(player, player:getHealth(), player:getMaxHealth()) + onManaChange(player, player:getMana(), player:getMaxMana()) + onMagicLevelChange(player, player:getMagicLevel(), player:getMagicLevelPercent()) + if not profileChange then + onStatesChange(player, player:getStates(), 0) + end + onHealthChange(player, player:getHealth(), player:getMaxHealth()) + onManaChange(player, player:getMana(), player:getMaxMana()) + onLevelChange(player, player:getLevel(), player:getLevelPercent()) + + for i = Skill.Fist, Skill.ManaLeechAmount do + onSkillChange(player, i, player:getSkillLevel(i), player:getSkillLevelPercent(i)) + onBaseSkillChange(player, i, player:getSkillBaseLevel(i)) + end + + topBar.skills.onGeometryChange = setSkillsLayout +end + +function refreshVisibleBars() + local ids = {"Experience", "Magic", "Axe", "Club", "Distance", "Fist", "Shielding", + "Sword", "Fishing"} + + for i, id in ipairs(ids) do + local panel = topBar[id] or topBar.skills[id] + + if panel then + -- experience is exeption + if id == "Experience" then + if not settings[id] then + panel:setVisible(true) + end + else + panel:setVisible(settings[id] or false) + end + end + end +end + +function setSkillsLayout() + local visible = 0 + local skills = topBar.skills + local width = skills:getWidth() + + for i, child in ipairs(skills:getChildren()) do + visible = child:isVisible() and visible + 1 or visible + end + + local many = visible > 1 + width = many and (width / 2) or width + + skills:getLayout():setCellSize({width = width, height = 19}) +end + +function offline() + local player = g_game.getLocalPlayer() + + if player then onStatesChange(player, 0, player:getStates()) end + save() +end + +function toggleIcon(bitChanged) + local content = states + if not content then return end + + local icon = content:getChildById(Icons[bitChanged].id) + if icon then + icon:destroy() + else + icon = loadIcon(bitChanged) + icon:setParent(content) + end +end + +function loadIcon(bitChanged) + local icon = g_ui.createWidget('ConditionWidget', content) + icon:setId(Icons[bitChanged].id) + icon:setImageSource(Icons[bitChanged].path) + icon:setTooltip(Icons[bitChanged].tooltip) + return icon +end + +function onHealthChange(localPlayer, health, maxHealth) + if not healthBar then return end + if health > maxHealth then maxHealth = health end + + local healthPercent = (health / maxHealth) * 100 + healthBar:setText(comma_value(health) .. ' / ' .. comma_value(maxHealth)) + healthBar:setValue(health, 0, maxHealth) + healthBar:setPercent(healthPercent) + + if healthPercent > 92 then + healthBar:setBackgroundColor("#00BC00FF") + elseif healthPercent > 60 then + healthBar:setBackgroundColor("#50A150FF") + elseif healthPercent > 30 then + healthBar:setBackgroundColor("#A1A100FF") + elseif healthPercent > 8 then + healthBar:setBackgroundColor("#BF0A0AFF") + elseif healthPercent > 3 then + healthBar:setBackgroundColor("#910F0FFF") + else + healthBar:setBackgroundColor("#850C0CFF") + end +end + +function onManaChange(localPlayer, mana, maxMana) + if not manaBar then return end + if mana > maxMana then maxMana = mana end + + local manaPercent = (mana / maxMana) * 100 + if manaPercent < 0 then return end + manaBar:setText(comma_value(mana) .. ' / ' .. comma_value(maxMana)) + manaBar:setValue(mana, 0, maxMana) + manaBar:setPercent(manaPercent) +end + +function onLevelChange(localPlayer, value, percent) + if not topBar then return end + local experienceBar = topBar.Experience.progress + local levelLabel = topBar.Experience.level + experienceBar:setTooltip(tr(experienceTooltip, 100-percent, value + 1)) + experienceBar:setPercent(percent) + levelLabel:setText(value) +end + +function onStatesChange(localPlayer, now, old) + if now == old then return end + + local bitsChanged = bit32.bxor(now, old) + for i = 1, 32 do + local pow = math.pow(2, i - 1) + if pow > bitsChanged then break end + local bitChanged = bit32.band(bitsChanged, pow) + if bitChanged ~= 0 then toggleIcon(bitChanged) end + end +end + +function show() + if not g_game.isOnline() then return end + topBar:setVisible(g_settings.getBoolean("topBar", false)) +end + +function setupSkillPanel(id, parent, experience, defaultOff) + local widget = g_ui.createWidget('SkillPanel', parent) + widget:setId(id) + widget.level:setTooltip(id) + widget.icon:setTooltip(id) + widget.icon:setImageClip({x = iconsTable[id]*9, y = 0, width = 9,height = 9}) + + if not experience then + widget.progress:setBackgroundColor('#00c000') + widget.shop:setVisible(false) + widget.shop:disable() + widget.shop:setWidth(0) + widget.progress:setMarginRight(1) + end + + settings[id] = settings[id] ~= nil and settings[id] or defaultOff + if settings[id] == false then widget:setVisible(false) end + + -- breakers + widget.onGeometryChange = function() + local margin = widget.progress:getWidth() / 4 + local left = widget.left + local right = widget.right + + left:setMarginRight(margin) + right:setMarginRight(margin) + end + +end + +function menu(mouseButton) + if mouseButton ~= 2 then return end + + local menu = g_ui.createWidget('PopupMenu') + menu:setId("topBarMenu") + menu:setGameMenu(true) + + local expPanel = topBar.Experience + local start = expPanel:isVisible() and "Hide" or "Show" + menu:addOption(start .. " Experience Level", + function() toggleSkillPanel(id) end) + for i, child in ipairs(topBar.skills:getChildren()) do + local id = child:getId() + if id ~= "stats" then + local start = child:isVisible() and "Hide" or "Show" + menu:addOption(start .. " " .. id .. " Level", + function() toggleSkillPanel(id) end) + end + end + + menu:display(mousePos) + return true +end + +function setupSkills() + local t = { + "Experience", "Magic", "Axe", "Club", "Distance", "Fist", "Shielding", + "Sword", "Fishing" + } + + for i, id in ipairs(t) do + if not topBar[id] and not topBar.skills[id] then + setupSkillPanel(id, i == 1 and topBar or topBar.skills, i == 1, + i == 1) + end + end + + local child = topBar.Experience + topBar:moveChildToIndex(child, 2) +end + +function toggleSkillPanel(id) + if not topBar then return end + local panel = topBar.skills[id] + panel = panel or topBar.Experience + if not panel then return end + + panel:setVisible(not panel:isVisible()) + settings[id] = panel:isVisible() + setSkillsLayout() +end + +function setSkillValue(id, value) + if not topBar then return end + local panel = topBar.skills[id] + if not panel then return end + + panel.level:setText(value) +end + +function setSkillPercent(id, percent, tooltip) + if not topBar then return end + local panel = topBar.skills[id] + if not panel then return end + + panel.progress:setPercent(math.floor(percent)) +end + +function setSkillBase(id, value, baseValue) + if not topBar then return end + local panel = topBar.skills[id] + if not panel then return end + + local progress = topBar.skills[id].progress + local progressDesc = "You have " .. 100 - math.floor(progress:getPercent()) .. " percent to go" + local level = topBar.skills[id].level + + if baseValue <= 0 or value < 0 then return end + + if value > baseValue then + level:setColor('#008b00') -- green + progress:setTooltip(value .. " = " .. baseValue .. ' + ' .. + (value - baseValue) .. "\n" .. progressDesc) + elseif value < baseValue then + level:setColor('#b22222') -- red + progress:setTooltip(baseValue .. ' ' .. (value - baseValue)) + else + level:setColor('#bbbbbb') -- default + progress:removeTooltip() + end + +end + +function onMagicLevelChange(localPlayer, magiclevel, percent) + setSkillValue('Magic', magiclevel) + setSkillPercent('Magic', percent) + + onBaseMagicLevelChange(localPlayer, localPlayer:getBaseMagicLevel()) +end + +function onBaseMagicLevelChange(localPlayer, baseMagicLevel) + setSkillBase('Magic', localPlayer:getMagicLevel(), baseMagicLevel) +end + +function onSkillChange(localPlayer, id, level, percent) + id = id + 1 + local t = { + "Fist", "Club", "Sword", "Axe", "Distance", "Shielding", "Fishing" + } + + -- imbues, ignore + if id > #t then return end + + setSkillValue(t[id], level) + setSkillPercent(t[id], percent) + + setSkillBase(t[id], level, localPlayer:getSkillBaseLevel(id - 1)) +end + +function onBaseSkillChange(localPlayer, id, baseLevel) + id = id + 1 + local t = { + "Fist", "Club", "Sword", "Axe", "Distance", "Shielding", "Fishing" + } + + -- imbues, ignore + if id > #t then return end + + setSkillBase(id, localPlayer:getSkillLevel(id), baseLevel) +end + +function save() + local settingsFile = modules.client_profiles.getSettingsFilePath("topbar.json") + + local status, result = pcall(function() return json.encode(settings, 2) end) + if not status then + return onError( + "Error while saving top bar settings. Data won't be saved. Details: " .. + result) + end + + if result:len() > 100 * 1024 * 1024 then + return onError( + "Something went wrong, file is above 100MB, won't be saved") + end + + g_resources.writeFileContents(settingsFile, result) +end + +function load() + local settingsFile = modules.client_profiles.getSettingsFilePath("topbar.json") + + if g_resources.fileExists(settingsFile) then + local status, result = pcall(function() + return json.decode(g_resources.readFileContents(settingsFile)) + end) + if not status then + return onError( + "Error while reading top bar settings file. To fix this problem you can delete storage.json. Details: " .. + result) + end + settings = result + else + settings = {} + end +end diff --git a/800OTClient/modules/game_topbar/topbar.otmod b/800OTClient/modules/game_topbar/topbar.otmod new file mode 100644 index 0000000..dec0669 --- /dev/null +++ b/800OTClient/modules/game_topbar/topbar.otmod @@ -0,0 +1,10 @@ +Module + name: game_topbar + description: Customizable Top Bar from Cipsoft's Tibia 12 Client + author: Vithrax + website: discord_Vithrax#5814 + sandboxed: true + autoload: true + scripts: [ topbar ] + @onLoad: init() + @onUnload: terminate() \ No newline at end of file diff --git a/800OTClient/modules/game_topbar/topbar.otui b/800OTClient/modules/game_topbar/topbar.otui new file mode 100644 index 0000000..be8ce7e --- /dev/null +++ b/800OTClient/modules/game_topbar/topbar.otui @@ -0,0 +1,121 @@ +StatsPanel < Panel + height: 19 + + ProgressBar + id: health + anchors.left: parent.left + anchors.right: states.left + anchors.top: states.top + anchors.bottom: states.bottom + margin-right: 7 + + ProgressBar + id: mana + anchors.left: states.right + anchors.right: parent.right + anchors.top: states.top + anchors.bottom: states.bottom + margin-left: 7 + background-color: #0060d5 + + FlatPanel + id: states + padding: 1 + size: 150 18 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + + Panel + id: box + image-source: /images/ui/dark_background + anchors.fill: parent + layout: + type: horizontalBox + +SkillPanel < Panel + height: 19 + + UIWidget + id: level + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + text: 999 + font: verdana-11px-rounded + text-align: center + margin-left: 7 + + UIWidget + id: icon + anchors.left: prev.right + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/topbar/icons + size: 9 9 + image-clip: 0 0 9 9 + margin-left: 7 + + ProgressBar + id: progress + anchors.left: prev.right + margin-left: 7 + anchors.right: next.left + margin-right: 7 + anchors.verticalCenter: parent.verticalCenter + background-color: #c00000 + height: 6 + border: 1 black + + UIWidget + id: shop + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/topbar/boost + size: 76 14 + @onClick: modules.game_shop.show() + image-clip: 0 0 76 14 + + $pressed: + image-clip: 0 14 76 14 + + VerticalSeparator + id: left + anchors.top: progress.top + margin-top: -2 + anchors.bottom: progress.bottom + margin-bottom: -2 + anchors.right: progress.horizontalCenter + + VerticalSeparator + id: middle + anchors.top: progress.top + margin-top: -2 + anchors.bottom: progress.bottom + margin-bottom: -2 + anchors.right: progress.horizontalCenter + + VerticalSeparator + id: right + anchors.top: progress.top + margin-top: -2 + anchors.bottom: progress.bottom + margin-bottom: -2 + anchors.right: progress.right + +TopBar < Panel + id: topbar + focusable: false + padding-top: 4 + padding-left: 7 + padding-right: 7 + layout: + type: verticalBox + fit-children: true + + StatsPanel + id: stats + + Panel + id: skills + layout: + type: grid + num-columns: 2 + fit-children: true \ No newline at end of file diff --git a/800OTClient/modules/game_unjustifiedpoints/unjustifiedpoints.lua b/800OTClient/modules/game_unjustifiedpoints/unjustifiedpoints.lua new file mode 100644 index 0000000..7538659 --- /dev/null +++ b/800OTClient/modules/game_unjustifiedpoints/unjustifiedpoints.lua @@ -0,0 +1,148 @@ +unjustifiedPointsWindow = nil +unjustifiedPointsButton = nil +contentsPanel = nil + +openPvpSituationsLabel = nil +currentSkullWidget = nil +skullTimeLabel = nil + +dayProgressBar = nil +weekProgressBar = nil +monthProgressBar = nil + +daySkullWidget = nil +weekSkullWidget = nil +monthSkullWidget = nil + +function init() + connect(g_game, { onGameStart = online, + onUnjustifiedPointsChange = onUnjustifiedPointsChange, + onOpenPvpSituationsChange = onOpenPvpSituationsChange }) + connect(LocalPlayer, { onSkullChange = onSkullChange } ) + + unjustifiedPointsButton = modules.client_topmenu.addRightGameToggleButton('unjustifiedPointsButton', + tr('Unjustified Points'), '/images/topbuttons/unjustifiedpoints', toggle) + unjustifiedPointsButton:setOn(true) + unjustifiedPointsButton:hide() + + unjustifiedPointsWindow = g_ui.loadUI('unjustifiedpoints', modules.game_interface.getRightPanel()) + unjustifiedPointsWindow:disableResize() + unjustifiedPointsWindow:setup() + + contentsPanel = unjustifiedPointsWindow:getChildById('contentsPanel') + + openPvpSituationsLabel = contentsPanel:getChildById('openPvpSituationsLabel') + currentSkullWidget = contentsPanel:getChildById('currentSkullWidget') + skullTimeLabel = contentsPanel:getChildById('skullTimeLabel') + + dayProgressBar = contentsPanel:getChildById('dayProgressBar') + weekProgressBar = contentsPanel:getChildById('weekProgressBar') + monthProgressBar = contentsPanel:getChildById('monthProgressBar') + daySkullWidget = contentsPanel:getChildById('daySkullWidget') + weekSkullWidget = contentsPanel:getChildById('weekSkullWidget') + monthSkullWidget = contentsPanel:getChildById('monthSkullWidget') + + if g_game.isOnline() then + online() + end +end + +function terminate() + disconnect(g_game, { onGameStart = online, + onUnjustifiedPointsChange = onUnjustifiedPointsChange, + onOpenPvpSituationsChange = onOpenPvpSituationsChange }) + disconnect(LocalPlayer, { onSkullChange = onSkullChange } ) + + unjustifiedPointsWindow:destroy() + unjustifiedPointsButton:destroy() +end + +function onMiniWindowClose() + unjustifiedPointsButton:setOn(false) +end + +function toggle() + if unjustifiedPointsButton:isOn() then + unjustifiedPointsWindow:close() + unjustifiedPointsButton:setOn(false) + else + unjustifiedPointsWindow:open() + unjustifiedPointsButton:setOn(true) + end +end + +function online() + if g_game.getFeature(GameUnjustifiedPoints) then + unjustifiedPointsButton:show() + else + unjustifiedPointsButton:hide() + unjustifiedPointsWindow:close() + end + + refresh() +end + +function refresh() + local localPlayer = g_game.getLocalPlayer() + + local unjustifiedPoints = g_game.getUnjustifiedPoints() + onUnjustifiedPointsChange(unjustifiedPoints) + + onSkullChange(localPlayer, localPlayer:getSkull()) + onOpenPvpSituationsChange(g_game.getOpenPvpSituations()) +end + +function onSkullChange(localPlayer, skull) + if not localPlayer:isLocalPlayer() then return end + + if skull == SkullRed or skull == SkullBlack then + currentSkullWidget:setIcon(getSkullImagePath(skull)) + currentSkullWidget:setTooltip('Remaining skull time') + else + currentSkullWidget:setIcon('') + currentSkullWidget:setTooltip('You have no skull') + end + + daySkullWidget:setIcon(getSkullImagePath(getNextSkullId(skull))) + weekSkullWidget:setIcon(getSkullImagePath(getNextSkullId(skull))) + monthSkullWidget:setIcon(getSkullImagePath(getNextSkullId(skull))) +end + +function onOpenPvpSituationsChange(amount) + openPvpSituationsLabel:setText(amount) +end + +local function getColorByKills(kills) + if kills < 2 then + return 'red' + elseif kills < 3 then + return 'yellow' + end + + return 'green' +end + +function onUnjustifiedPointsChange(unjustifiedPoints) + if unjustifiedPoints.skullTime == 0 then + skullTimeLabel:setText('No skull') + skullTimeLabel:setTooltip('You have no skull') + else + skullTimeLabel:setText(unjustifiedPoints.skullTime .. ' days') + skullTimeLabel:setTooltip('Remaining skull time') + end + + dayProgressBar:setValue(unjustifiedPoints.killsDay, 0, 100) + dayProgressBar:setBackgroundColor(getColorByKills(unjustifiedPoints.killsDayRemaining)) + dayProgressBar:setTooltip(string.format('Unjustified points gained during the last 24 hours.\n%i kill%s left.', unjustifiedPoints.killsDayRemaining, (unjustifiedPoints.killsDayRemaining == 1 and '' or 's'))) + dayProgressBar:setText(string.format('%i kill%s left', unjustifiedPoints.killsDayRemaining, (unjustifiedPoints.killsDayRemaining == 1 and '' or 's'))) + + weekProgressBar:setValue(unjustifiedPoints.killsWeek, 0, 100) + weekProgressBar:setBackgroundColor(getColorByKills(unjustifiedPoints.killsWeekRemaining)) + weekProgressBar:setTooltip(string.format('Unjustified points gained during the last 7 days.\n%i kill%s left.', unjustifiedPoints.killsWeekRemaining, (unjustifiedPoints.killsWeekRemaining == 1 and '' or 's'))) + weekProgressBar:setText(string.format('%i kill%s left', unjustifiedPoints.killsWeekRemaining, (unjustifiedPoints.killsWeekRemaining == 1 and '' or 's'))) + + monthProgressBar:setValue(unjustifiedPoints.killsMonth, 0, 100) + monthProgressBar:setBackgroundColor(getColorByKills(unjustifiedPoints.killsMonthRemaining)) + monthProgressBar:setTooltip(string.format('Unjustified points gained during the last 30 days.\n%i kill%s left.', unjustifiedPoints.killsMonthRemaining, (unjustifiedPoints.killsMonthRemaining == 1 and '' or 's'))) + monthProgressBar:setText(string.format('%i kill%s left', unjustifiedPoints.killsMonthRemaining, (unjustifiedPoints.killsMonthRemaining == 1 and '' or 's'))) +end diff --git a/800OTClient/modules/game_unjustifiedpoints/unjustifiedpoints.otmod b/800OTClient/modules/game_unjustifiedpoints/unjustifiedpoints.otmod new file mode 100644 index 0000000..178a414 --- /dev/null +++ b/800OTClient/modules/game_unjustifiedpoints/unjustifiedpoints.otmod @@ -0,0 +1,8 @@ +Module + name: game_unjustifiedpoints + description: View unjustified points + author: Summ + sandboxed: true + scripts: [ unjustifiedpoints ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_unjustifiedpoints/unjustifiedpoints.otui b/800OTClient/modules/game_unjustifiedpoints/unjustifiedpoints.otui new file mode 100644 index 0000000..8f0d663 --- /dev/null +++ b/800OTClient/modules/game_unjustifiedpoints/unjustifiedpoints.otui @@ -0,0 +1,81 @@ +SkullProgressBar < ProgressBar + height: 13 + margin: 4 18 0 10 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + +SkullWidget < UIWidget + size: 13 13 + margin-right: 2 + anchors.right: parent.right + image-source: /images/game/skull_socket + +MiniWindow + id: unjustifiedPointsWindow + !text: tr('Unjustified Points') + height: 114 + icon: /images/topbuttons/unjustifiedpoints + @onClose: modules.game_unjustifiedpoints.onMiniWindowClose() + &save: true + &autoOpen: false + + MiniWindowContents + Label + anchors.top: parent.top + anchors.left: parent.left + !text: tr('Open PvP') + !tooltip: tr('Open PvP Situations') + phantom: false + margin-top: 2 + margin-left: 10 + + Label + id: openPvpSituationsLabel + anchors.top: prev.bottom + anchors.left: parent.left + font: verdana-11px-rounded + margin-left: 12 + phantom: false + + Label + anchors.top: parent.top + anchors.right: parent.right + !text: tr('Skull Time') + margin-top: 2 + margin-right: 10 + + SkullWidget + id: currentSkullWidget + anchors.top: prev.bottom + margin-right: 10 + + Label + id: skullTimeLabel + anchors.top: prev.top + anchors.right: prev.left + font: verdana-11px-rounded + margin-right: 6 + phantom: false + + SkullProgressBar + id: dayProgressBar + margin-top: 10 + + SkullWidget + id: daySkullWidget + anchors.top: prev.top + + SkullProgressBar + id: weekProgressBar + + SkullWidget + id: weekSkullWidget + anchors.top: prev.top + + SkullProgressBar + id: monthProgressBar + + SkullWidget + id: monthSkullWidget + anchors.top: prev.top diff --git a/800OTClient/modules/game_viplist/addvip.otui b/800OTClient/modules/game_viplist/addvip.otui new file mode 100644 index 0000000..bc88a2a --- /dev/null +++ b/800OTClient/modules/game_viplist/addvip.otui @@ -0,0 +1,39 @@ +MainWindow + size: 256 128 + !text: tr('Add to VIP list') + @onEnter: modules.game_viplist.addVip() + @onEscape: modules.game_viplist.destroyAddWindow() + + Label + !text: tr('Please enter a character name:') + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + TextEdit + id: name + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 4 + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + margin-bottom: 10 + + Button + !text: tr('Ok') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + @onClick: modules.game_viplist.addVip() + + Button + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: modules.game_viplist.destroyAddWindow() diff --git a/800OTClient/modules/game_viplist/editvip.otui b/800OTClient/modules/game_viplist/editvip.otui new file mode 100644 index 0000000..096489f --- /dev/null +++ b/800OTClient/modules/game_viplist/editvip.otui @@ -0,0 +1,138 @@ +IconButton < CheckBox + size: 20 20 + image-source: /images/game/viplist/vipcheckbox + image-size: 20 20 + image-border: 3 + margin: 2 + icon-source: /images/game/viplist/icons + icon-size: 12 12 + icon-rect: 0 0 12 12 + icon-clip: 0 0 12 12 + icon-offset: 4 6 + + $first: + margin-left: 0 + + $!checked: + image-clip: 26 0 26 26 + + $hover !checked: + image-clip: 78 0 26 26 + + $checked: + image-clip: 0 0 26 26 + + $hover checked: + image-clip: 52 0 26 26 + +MainWindow + size: 272 170 + !text: tr('Edit VIP list entry') + + Label + id: nameLabel + text: Name + anchors.top: parent.top + anchors.left: parent.left + color: green + width: 180 + + Label + !text: tr('Description') .. ':' + anchors.top: prev.bottom + anchors.left: parent.left + text-offset: 0 3 + height: 20 + margin-top: 5 + + TextEdit + id: descriptionText + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin: 0 5 + + Label + !text: tr('Notify-Login') .. ':' + anchors.top: prev.bottom + anchors.left: parent.left + text-offset: 0 3 + height: 20 + margin-top: 5 + + CheckBox + id: checkBoxNotify + anchors.top: prev.top + anchors.left: prev.right + margin: 2 6 + + UIWidget + layout: horizontalBox + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 24 + + IconButton + id: icon0 + + IconButton + id: icon1 + icon-clip: 12 0 12 12 + + IconButton + id: icon2 + icon-clip: 24 0 12 12 + + IconButton + id: icon3 + icon-clip: 36 0 12 12 + + IconButton + id: icon4 + icon-clip: 48 0 12 12 + + IconButton + id: icon5 + icon-clip: 60 0 12 12 + + IconButton + id: icon6 + icon-clip: 72 0 12 12 + + IconButton + id: icon7 + icon-clip: 84 0 12 12 + + IconButton + id: icon8 + icon-clip: 96 0 12 12 + + IconButton + id: icon9 + icon-clip: 108 0 12 12 + + IconButton + id: icon10 + icon-clip: 120 0 12 12 + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + margin-bottom: 10 + + Button + id: buttonOK + !text: tr('Ok') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + + Button + id: buttonCancel + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom diff --git a/800OTClient/modules/game_viplist/viplist.lua b/800OTClient/modules/game_viplist/viplist.lua new file mode 100644 index 0000000..195664c --- /dev/null +++ b/800OTClient/modules/game_viplist/viplist.lua @@ -0,0 +1,426 @@ +vipWindow = nil +vipButton = nil +addVipWindow = nil +editVipWindow = nil +vipInfo = {} + +function init() + connect(g_game, { onGameStart = refresh, + onGameEnd = clear, + onAddVip = onAddVip, + onVipStateChange = onVipStateChange }) + + + g_keyboard.bindKeyDown('Ctrl+P', toggle) + + vipButton = modules.client_topmenu.addRightGameToggleButton('vipListButton', tr('VIP List') .. ' (Ctrl+P)', '/images/topbuttons/viplist', toggle, false, 3) + vipButton:setOn(true) + vipWindow = g_ui.loadUI('viplist', modules.game_interface.getRightPanel()) + + if not g_game.getFeature(GameAdditionalVipInfo) then + loadVipInfo() + end + refresh() + vipWindow:setup() +end + +function terminate() + g_keyboard.unbindKeyDown('Ctrl+P') + disconnect(g_game, { onGameStart = refresh, + onGameEnd = clear, + onAddVip = onAddVip, + onVipStateChange = onVipStateChange }) + + if not g_game.getFeature(GameAdditionalVipInfo) then + saveVipInfo() + end + + if addVipWindow then + addVipWindow:destroy() + end + + if editVipWindow then + editVipWindow:destroy() + end + + vipWindow:destroy() + vipButton:destroy() +end + +function loadVipInfo() + local settings = g_settings.getNode('VipList') + if not settings then + vipInfo = {} + return + end + vipInfo = settings['VipInfo'] or {} +end + +function saveVipInfo() + settings = {} + settings['VipInfo'] = vipInfo + g_settings.mergeNode('VipList', settings) +end + + +function refresh() + clear() + for id,vip in pairs(g_game.getVips()) do + onAddVip(id, unpack(vip)) + end + + vipWindow:setContentMinimumHeight(38) +end + +function clear() + local vipList = vipWindow:getChildById('contentsPanel') + vipList:destroyChildren() +end + +function toggle() + if vipButton:isOn() then + vipWindow:close() + vipButton:setOn(false) + else + vipWindow:open() + vipButton:setOn(true) + end +end + +function onMiniWindowClose() + vipButton:setOn(false) +end + +function createAddWindow() + if not addVipWindow then + addVipWindow = g_ui.displayUI('addvip') + end +end + +function createEditWindow(widget) + if editVipWindow then + return + end + + editVipWindow = g_ui.displayUI('editvip') + + local name = widget:getText() + local id = widget:getId():sub(4) + + local okButton = editVipWindow:getChildById('buttonOK') + local cancelButton = editVipWindow:getChildById('buttonCancel') + + local nameLabel = editVipWindow:getChildById('nameLabel') + nameLabel:setText(name) + + local descriptionText = editVipWindow:getChildById('descriptionText') + descriptionText:appendText(widget:getTooltip()) + + local notifyCheckBox = editVipWindow:getChildById('checkBoxNotify') + notifyCheckBox:setChecked(widget.notifyLogin) + + local iconRadioGroup = UIRadioGroup.create() + for i = VipIconFirst, VipIconLast do + iconRadioGroup:addWidget(editVipWindow:recursiveGetChildById('icon' .. i)) + end + iconRadioGroup:selectWidget(editVipWindow:recursiveGetChildById('icon' .. widget.iconId)) + + local cancelFunction = function() + editVipWindow:destroy() + iconRadioGroup:destroy() + editVipWindow = nil + end + + local saveFunction = function() + local vipList = vipWindow:getChildById('contentsPanel') + if not widget or not vipList:hasChild(widget) then + cancelFunction() + return + end + + local name = widget:getText() + local state = widget.vipState + local description = descriptionText:getText() + local iconId = tonumber(iconRadioGroup:getSelectedWidget():getId():sub(5)) + local notify = notifyCheckBox:isChecked() + + if g_game.getFeature(GameAdditionalVipInfo) then + g_game.editVip(id, description, iconId, notify) + else + if notify ~= false or #description > 0 or iconId > 0 then + vipInfo[id] = {description = description, iconId = iconId, notifyLogin = notify} + else + vipInfo[id] = nil + end + end + + widget:destroy() + onAddVip(id, name, state, description, iconId, notify) + + editVipWindow:destroy() + iconRadioGroup:destroy() + editVipWindow = nil + end + + cancelButton.onClick = cancelFunction + okButton.onClick = saveFunction + + editVipWindow.onEscape = cancelFunction + editVipWindow.onEnter = saveFunction +end + +function destroyAddWindow() + addVipWindow:destroy() + addVipWindow = nil +end + +function addVip() + g_game.addVip(addVipWindow:getChildById('name'):getText()) + destroyAddWindow() +end + +function removeVip(widgetOrName) + if not widgetOrName then + return + end + + local widget + local vipList = vipWindow:getChildById('contentsPanel') + if type(widgetOrName) == 'string' then + local entries = vipList:getChildren() + for i = 1, #entries do + if entries[i]:getText():lower() == widgetOrName:lower() then + widget = entries[i] + break + end + end + if not widget then + return + end + else + widget = widgetOrName + end + + if widget then + local id = widget:getId():sub(4) + g_game.removeVip(id) + vipList:removeChild(widget) + if vipInfo[id] and g_game.getFeature(GameAdditionalVipInfo) then + vipInfo[id] = nil + end + end +end + +function hideOffline(state) + settings = {} + settings['hideOffline'] = state + g_settings.mergeNode('VipList', settings) + + refresh() +end + +function isHiddingOffline() + local settings = g_settings.getNode('VipList') + if not settings then + return false + end + return settings['hideOffline'] +end + +function getSortedBy() + local settings = g_settings.getNode('VipList') + if not settings or not settings['sortedBy'] then + return 'status' + end + return settings['sortedBy'] +end + +function sortBy(state) + settings = {} + settings['sortedBy'] = state + g_settings.mergeNode('VipList', settings) + + refresh() +end + +function onAddVip(id, name, state, description, iconId, notify) + if not name or name:len() == 0 then + return + end + + local vipList = vipWindow:getChildById('contentsPanel') + local childrenCount = vipList:getChildCount() + for i=1,childrenCount do + local child = vipList:getChildByIndex(i) + if child:getText() == name then + return -- don't add duplicated vips + end + end + + local label = g_ui.createWidget('VipListLabel') + label.onMousePress = onVipListLabelMousePress + label:setId('vip' .. id) + label:setText(name) + + if not g_game.getFeature(GameAdditionalVipInfo) then + local tmpVipInfo = vipInfo[tostring(id)] + label.iconId = 0 + label.notifyLogin = false + if tmpVipInfo then + if tmpVipInfo.iconId then + label:setImageClip(torect((tmpVipInfo.iconId * 12) .. ' 0 12 12')) + label.iconId = tmpVipInfo.iconId + end + if tmpVipInfo.description then + label:setTooltip(tmpVipInfo.description) + end + label.notifyLogin = tmpVipInfo.notifyLogin or false + end + else + label:setTooltip(description) + label:setImageClip(torect((iconId * 12) .. ' 0 12 12')) + label.iconId = iconId + label.notifyLogin = notify + end + + if state == VipState.Online then + label:setColor('#00ff00') + elseif state == VipState.Pending then + label:setColor('#ffca38') + else + label:setColor('#ff0000') + end + + label.vipState = state + + label:setPhantom(false) + connect(label, { onDoubleClick = function () g_game.openPrivateChannel(label:getText()) return true end } ) + + if state == VipState.Offline and isHiddingOffline() then + label:setVisible(false) + end + + local nameLower = name:lower() + local childrenCount = vipList:getChildCount() + + for i=1,childrenCount do + local child = vipList:getChildByIndex(i) + if (state == VipState.Online and child.vipState ~= VipState.Online and getSortedBy() == 'status') + or (label.iconId > child.iconId and getSortedBy() == 'type') then + vipList:insertChild(i, label) + return + end + + if (((state ~= VipState.Online and child.vipState ~= VipState.Online) or (state == VipState.Online and child.vipState == VipState.Online)) and getSortedBy() == 'status') + or (label.iconId == child.iconId and getSortedBy() == 'type') or getSortedBy() == 'name' then + + local childText = child:getText():lower() + local length = math.min(childText:len(), nameLower:len()) + + for j=1,length do + if nameLower:byte(j) < childText:byte(j) then + vipList:insertChild(i, label) + return + elseif nameLower:byte(j) > childText:byte(j) then + break + elseif j == nameLower:len() then -- We are at the end of nameLower, and its shorter than childText, thus insert before + vipList:insertChild(i, label) + return + end + end + end + end + + vipList:insertChild(childrenCount+1, label) +end + +function onVipStateChange(id, state) + local vipList = vipWindow:getChildById('contentsPanel') + local label = vipList:getChildById('vip' .. id) + if not label then + return + end + local name = label:getText() + local description = label:getTooltip() + local iconId = label.iconId + local notify = label.notifyLogin + label:destroy() + + onAddVip(id, name, state, description, iconId, notify) + + if notify and state ~= VipState.Pending then + modules.game_textmessage.displayFailureMessage(tr('%s has logged %s.', name, (state == VipState.Online and 'in' or 'out'))) + end +end + +function onVipListMousePress(widget, mousePos, mouseButton) + if mouseButton ~= MouseRightButton then return end + + local vipList = vipWindow:getChildById('contentsPanel') + + local menu = g_ui.createWidget('PopupMenu') + menu:setGameMenu(true) + menu:addOption(tr('Add new VIP'), function() createAddWindow() end) + + menu:addSeparator() + if not isHiddingOffline() then + menu:addOption(tr('Hide Offline'), function() hideOffline(true) end) + else + menu:addOption(tr('Show Offline'), function() hideOffline(false) end) + end + + if not(getSortedBy() == 'name') then + menu:addOption(tr('Sort by name'), function() sortBy('name') end) + end + + if not(getSortedBy() == 'status') then + menu:addOption(tr('Sort by status'), function() sortBy('status') end) + end + + if not(getSortedBy() == 'type') then + menu:addOption(tr('Sort by type'), function() sortBy('type') end) + end + + menu:display(mousePos) + + return true +end + +function onVipListLabelMousePress(widget, mousePos, mouseButton) + if mouseButton ~= MouseRightButton then return end + + local vipList = vipWindow:getChildById('contentsPanel') + + local menu = g_ui.createWidget('PopupMenu') + menu:setGameMenu(true) + menu:addOption(tr('Send Message'), function() g_game.openPrivateChannel(widget:getText()) end) + menu:addOption(tr('Add new VIP'), function() createAddWindow() end) + menu:addOption(tr('Edit %s', widget:getText()), function() if widget then createEditWindow(widget) end end) + menu:addOption(tr('Remove %s', widget:getText()), function() if widget then removeVip(widget) end end) + menu:addSeparator() + menu:addOption(tr('Copy Name'), function() g_window.setClipboardText(widget:getText()) end) + + if modules.game_console.getOwnPrivateTab() then + menu:addSeparator() + menu:addOption(tr('Invite to private chat'), function() g_game.inviteToOwnChannel(widget:getText()) end) + menu:addOption(tr('Exclude from private chat'), function() g_game.excludeFromOwnChannel(widget:getText()) end) + end + + if not isHiddingOffline() then + menu:addOption(tr('Hide Offline'), function() hideOffline(true) end) + else + menu:addOption(tr('Show Offline'), function() hideOffline(false) end) + end + + if not(getSortedBy() == 'name') then + menu:addOption(tr('Sort by name'), function() sortBy('name') end) + end + + if not(getSortedBy() == 'status') then + menu:addOption(tr('Sort by status'), function() sortBy('status') end) + end + + menu:display(mousePos) + + return true +end diff --git a/800OTClient/modules/game_viplist/viplist.otmod b/800OTClient/modules/game_viplist/viplist.otmod new file mode 100644 index 0000000..c88d44d --- /dev/null +++ b/800OTClient/modules/game_viplist/viplist.otmod @@ -0,0 +1,9 @@ +Module + name: game_viplist + description: Manage vip list window + author: baxnie, edubart + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ viplist ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/game_viplist/viplist.otui b/800OTClient/modules/game_viplist/viplist.otui new file mode 100644 index 0000000..d3eece3 --- /dev/null +++ b/800OTClient/modules/game_viplist/viplist.otui @@ -0,0 +1,26 @@ +VipListLabel < GameLabel + margin-top: 2 + text-offset: 16 0 + image-rect: 0 0 12 12 + image-clip: 0 0 12 12 + image-source: /images/game/viplist/icons + font: verdana-11px-monochrome + phantom: false + + $first: + margin-top: 5 + +MiniWindow + id: vipWindow + !text: tr('VIP List') + height: 100 + icon: /images/topbuttons/viplist + @onClose: modules.game_viplist.onMiniWindowClose() + &save: true + &autoOpen: false + + MiniWindowContents + layout: verticalBox + padding-left: 5 + padding-right: 5 + &onMousePress: modules.game_viplist.onVipListMousePress diff --git a/800OTClient/modules/game_walking/walking.lua b/800OTClient/modules/game_walking/walking.lua new file mode 100644 index 0000000..47f9499 --- /dev/null +++ b/800OTClient/modules/game_walking/walking.lua @@ -0,0 +1,433 @@ +smartWalkDirs = {} +smartWalkDir = nil +wsadWalking = false +nextWalkDir = nil +lastWalkDir = nil +lastFinishedStep = 0 +autoWalkEvent = nil +firstStep = true +walkLock = 0 +walkEvent = nil +lastWalk = 0 +lastTurn = 0 +lastTurnDirection = 0 +lastStop = 0 +lastManualWalk = 0 +autoFinishNextServerWalk = 0 +turnKeys = {} + +function init() + connect(g_game, { onTeleport = onTeleport }) + + connect(LocalPlayer, { + onPositionChange = onPositionChange, + onWalk = onWalk, + onWalkFinish = onWalkFinish, + onCancelWalk = onCancelWalk + }) + + modules.game_interface.getRootPanel().onFocusChange = stopSmartWalk + bindKeys() +end + +function terminate() + disconnect(g_game, { onTeleport = onTeleport }) + + disconnect(LocalPlayer, { + onPositionChange = onPositionChange, + onWalk = onWalk, + onWalkFinish = onWalkFinish + }) + removeEvent(autoWalkEvent) + stopSmartWalk() + unbindKeys() + disableWSAD() +end + +function bindKeys() + bindWalkKey('Up', North) + bindWalkKey('Right', East) + bindWalkKey('Down', South) + bindWalkKey('Left', West) + bindWalkKey('Numpad8', North) + bindWalkKey('Numpad9', NorthEast) + bindWalkKey('Numpad6', East) + bindWalkKey('Numpad3', SouthEast) + bindWalkKey('Numpad2', South) + bindWalkKey('Numpad1', SouthWest) + bindWalkKey('Numpad4', West) + bindWalkKey('Numpad7', NorthWest) + + bindTurnKey('Ctrl+Up', North) + bindTurnKey('Ctrl+Right', East) + bindTurnKey('Ctrl+Down', South) + bindTurnKey('Ctrl+Left', West) + bindTurnKey('Ctrl+Numpad8', North) + bindTurnKey('Ctrl+Numpad6', East) + bindTurnKey('Ctrl+Numpad2', South) + bindTurnKey('Ctrl+Numpad4', West) +end + +function unbindKeys() + unbindWalkKey('Up', North) + unbindWalkKey('Right', East) + unbindWalkKey('Down', South) + unbindWalkKey('Left', West) + unbindWalkKey('Numpad8', North) + unbindWalkKey('Numpad9', NorthEast) + unbindWalkKey('Numpad6', East) + unbindWalkKey('Numpad3', SouthEast) + unbindWalkKey('Numpad2', South) + unbindWalkKey('Numpad1', SouthWest) + unbindWalkKey('Numpad4', West) + unbindWalkKey('Numpad7', NorthWest) + + unbindTurnKey('Ctrl+Up', North) + unbindTurnKey('Ctrl+Right', East) + unbindTurnKey('Ctrl+Down', South) + unbindTurnKey('Ctrl+Left', West) + unbindTurnKey('Ctrl+Numpad8', North) + unbindTurnKey('Ctrl+Numpad6', East) + unbindTurnKey('Ctrl+Numpad2', South) + unbindTurnKey('Ctrl+Numpad4', West) +end + +function enableWSAD() + if wsadWalking then + return + end + wsadWalking = true + local player = g_game.getLocalPlayer() + if player then + player:lockWalk(100) -- 100 ms walk lock for all directions + end + + bindWalkKey("W", North) + bindWalkKey("D", East) + bindWalkKey("S", South) + bindWalkKey("A", West) + + bindTurnKey("Ctrl+W", North) + bindTurnKey("Ctrl+D", East) + bindTurnKey("Ctrl+S", South) + bindTurnKey("Ctrl+A", West) + + bindWalkKey("E", NorthEast) + bindWalkKey("Q", NorthWest) + bindWalkKey("C", SouthEast) + bindWalkKey("Z", SouthWest) +end + +function disableWSAD() + if not wsadWalking then + return + end + wsadWalking = false + + unbindWalkKey("W") + unbindWalkKey("D") + unbindWalkKey("S") + unbindWalkKey("A") + + unbindTurnKey("Ctrl+W") + unbindTurnKey("Ctrl+D") + unbindTurnKey("Ctrl+S") + unbindTurnKey("Ctrl+A") + + unbindWalkKey("E") + unbindWalkKey("Q") + unbindWalkKey("C") + unbindWalkKey("Z") +end + +function bindWalkKey(key, dir) + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.bindKeyDown(key, function() changeWalkDir(dir) end, gameRootPanel, true) + g_keyboard.bindKeyUp(key, function() changeWalkDir(dir, true) end, gameRootPanel, true) + g_keyboard.bindKeyPress(key, function(c, k, ticks) smartWalk(dir, ticks) end, gameRootPanel) +end + +function unbindWalkKey(key) + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.unbindKeyDown(key, gameRootPanel) + g_keyboard.unbindKeyUp(key, gameRootPanel) + g_keyboard.unbindKeyPress(key, gameRootPanel) +end + +function bindTurnKey(key, dir) + turnKeys[key] = dir + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.bindKeyDown(key, function() turn(dir, false) end, gameRootPanel) + g_keyboard.bindKeyPress(key, function() turn(dir, true) end, gameRootPanel) + g_keyboard.bindKeyUp(key, function() local player = g_game.getLocalPlayer() if player then player:lockWalk(200) end end, gameRootPanel) +end + +function unbindTurnKey(key) + turnKeys[key] = nil + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.unbindKeyDown(key, gameRootPanel) + g_keyboard.unbindKeyPress(key, gameRootPanel) + g_keyboard.unbindKeyUp(key, gameRootPanel) +end + +function stopSmartWalk() + smartWalkDirs = {} + smartWalkDir = nil +end + +function changeWalkDir(dir, pop) + while table.removevalue(smartWalkDirs, dir) do end + if pop then + if #smartWalkDirs == 0 then + stopSmartWalk() + return + end + else + table.insert(smartWalkDirs, 1, dir) + end + + smartWalkDir = smartWalkDirs[1] + if modules.client_options.getOption('smartWalk') and #smartWalkDirs > 1 then + for _,d in pairs(smartWalkDirs) do + if (smartWalkDir == North and d == West) or (smartWalkDir == West and d == North) then + smartWalkDir = NorthWest + break + elseif (smartWalkDir == North and d == East) or (smartWalkDir == East and d == North) then + smartWalkDir = NorthEast + break + elseif (smartWalkDir == South and d == West) or (smartWalkDir == West and d == South) then + smartWalkDir = SouthWest + break + elseif (smartWalkDir == South and d == East) or (smartWalkDir == East and d == South) then + smartWalkDir = SouthEast + break + end + end + end +end + +function smartWalk(dir, ticks) + walkEvent = scheduleEvent(function() + if g_keyboard.getModifiers() == KeyboardNoModifier then + local direction = smartWalkDir or dir + walk(direction, ticks) + return true + end + return false + end, 20) +end + +function canChangeFloorDown(pos) + pos.z = pos.z + 1 + toTile = g_map.getTile(pos) + return toTile and toTile:hasElevation(3) +end + +function canChangeFloorUp(pos) + pos.z = pos.z - 1 + toTile = g_map.getTile(pos) + return toTile and toTile:isWalkable() +end + +function onPositionChange(player, newPos, oldPos) +end + +function onWalk(player, newPos, oldPos) + if autoFinishNextServerWalk + 200 > g_clock.millis() then + player:finishServerWalking() + end +end + +function onTeleport(player, newPos, oldPos) + if not newPos or not oldPos then + return + end + -- floor change is also teleport + if math.abs(newPos.x - oldPos.x) >= 3 or math.abs(newPos.y - oldPos.y) >= 3 or math.abs(newPos.z - oldPos.z) >= 2 then + -- far teleport, lock walk for 100ms + walkLock = g_clock.millis() + g_settings.getNumber('walkTeleportDelay') + else + walkLock = g_clock.millis() + g_settings.getNumber('walkStairsDelay') + end + nextWalkDir = nil -- cancel autowalk +end + +function onWalkFinish(player) + lastFinishedStep = g_clock.millis() + if nextWalkDir ~= nil then + removeEvent(autoWalkEvent) + autoWalkEvent = addEvent(function() if nextWalkDir ~= nil then walk(nextWalkDir, 0) end end, false) + end +end + +function onCancelWalk(player) + player:lockWalk(50) +end + +function walk(dir, ticks) + lastManualWalk = g_clock.millis() + local player = g_game.getLocalPlayer() + if not player or g_game.isDead() or player:isDead() then + return + end + + if player:isWalkLocked() then + nextWalkDir = nil + return + end + + if g_game.isFollowing() then + g_game.cancelFollow() + end + + if player:isAutoWalking() then + if lastStop + 100 < g_clock.millis() then + lastStop = g_clock.millis() + player:stopAutoWalk() + g_game.stop() + end + end + + local dash = false + local ignoredCanWalk = false + if not g_game.getFeature(GameNewWalking) then + dash = g_settings.getBoolean("dash", false) + end + + local ticksToNextWalk = player:getStepTicksLeft() + if not player:canWalk(dir) then -- canWalk return false when previous walk is not finished or not confirmed by server + if dash then + ignoredCanWalk = true + else + if ticksToNextWalk < 500 and (lastWalkDir ~= dir or ticks == 0) then + nextWalkDir = dir + end + if ticksToNextWalk < 30 and lastFinishedStep + 400 > g_clock.millis() and nextWalkDir == nil then -- clicked walk 20 ms too early, try to execute again as soon possible to keep smooth walking + nextWalkDir = dir + end + return + end + end + + --if nextWalkDir ~= nil and lastFinishedStep + 200 < g_clock.millis() then + -- print("Cancel " .. nextWalkDir) + -- nextWalkDir = nil + --end + if nextWalkDir ~= nil and nextWalkDir ~= lastWalkDir then + dir = nextWalkDir + end + + local toPos = player:getPrewalkingPosition(true) + if dir == North then + toPos.y = toPos.y - 1 + elseif dir == East then + toPos.x = toPos.x + 1 + elseif dir == South then + toPos.y = toPos.y + 1 + elseif dir == West then + toPos.x = toPos.x - 1 + elseif dir == NorthEast then + toPos.x = toPos.x + 1 + toPos.y = toPos.y - 1 + elseif dir == SouthEast then + toPos.x = toPos.x + 1 + toPos.y = toPos.y + 1 + elseif dir == SouthWest then + toPos.x = toPos.x - 1 + toPos.y = toPos.y + 1 + elseif dir == NorthWest then + toPos.x = toPos.x - 1 + toPos.y = toPos.y - 1 + end + local toTile = g_map.getTile(toPos) + + if walkLock >= g_clock.millis() and lastWalkDir == dir then + nextWalkDir = nil + return + end + + if firstStep and lastWalkDir == dir and lastWalk + g_settings.getNumber('walkFirstStepDelay') > g_clock.millis() then + firstStep = false + walkLock = lastWalk + g_settings.getNumber('walkFirstStepDelay') + return + end + + if dash and lastWalkDir == dir and lastWalk + 50 > g_clock.millis() then + return + end + + firstStep = (not player:isWalking() and lastFinishedStep + 100 < g_clock.millis() and walkLock + 100 < g_clock.millis()) + if player:isServerWalking() and not dash then + walkLock = walkLock + math.max(g_settings.getNumber('walkFirstStepDelay'), 100) + end + + nextWalkDir = nil + removeEvent(autoWalkEvent) + autoWalkEvent = nil + local preWalked = false + if toTile and toTile:isWalkable() then + if not player:isServerWalking() and not ignoredCanWalk then + player:preWalk(dir) + preWalked = true + end + else + local playerTile = player:getTile() + if (playerTile and playerTile:hasElevation(3) and canChangeFloorUp(toPos)) or canChangeFloorDown(toPos) or (toTile and toTile:isEmpty() and not toTile:isBlocking()) then + player:lockWalk(100) + elseif player:isServerWalking() then + g_game.stop() + return + elseif not toTile then + player:lockWalk(100) -- bug fix for missing stairs down on map + else + if g_app.isMobile() and dir <= Directions.West then + turn(dir, ticks > 0) + end + return -- not walkable tile + end + end + + if player:isServerWalking() and not dash then + g_game.stop() + player:finishServerWalking() + autoFinishNextServerWalk = g_clock.millis() + 200 + end + g_game.walk(dir, preWalked) + + if not firstStep and lastWalkDir ~= dir then + walkLock = g_clock.millis() + g_settings.getNumber('walkTurnDelay') + end + + lastWalkDir = dir + lastWalk = g_clock.millis() + return true +end + +function turn(dir, repeated) + local player = g_game.getLocalPlayer() + if player:isWalking() and player:getWalkDirection() == dir and not player:isServerWalking() then + return + end + + removeEvent(walkEvent) + + if not repeated or (lastTurn + 100 < g_clock.millis()) then + g_game.turn(dir) + changeWalkDir(dir) + lastTurn = g_clock.millis() + if not repeated then + lastTurn = g_clock.millis() + 50 + end + lastTurnDirection = dir + nextWalkDir = nil + player:lockWalk(g_settings.getNumber('walkCtrlTurnDelay')) + end +end + +function checkTurn() + for keys, direction in pairs(turnKeys) do + if g_keyboard.areKeysPressed(keys) then + turn(direction, false) + end + end +end diff --git a/800OTClient/modules/game_walking/walking.otmod b/800OTClient/modules/game_walking/walking.otmod new file mode 100644 index 0000000..9e4d991 --- /dev/null +++ b/800OTClient/modules/game_walking/walking.otmod @@ -0,0 +1,9 @@ +Module + name: game_walking + description: Control walking and turns + author: otclient.ovh + website: http://otclient.ovh + scripts: [ walking ] + dependencies: [ game_interface ] + @onLoad: init() + @onUnload: terminate() diff --git a/800OTClient/modules/gamelib/const.lua b/800OTClient/modules/gamelib/const.lua new file mode 100644 index 0000000..be9e7e0 --- /dev/null +++ b/800OTClient/modules/gamelib/const.lua @@ -0,0 +1,403 @@ +-- @docconsts @{ + +FloorHigher = 0 +FloorLower = 15 + +SkullNone = 0 +SkullYellow = 1 +SkullGreen = 2 +SkullWhite = 3 +SkullRed = 4 +SkullBlack = 5 +SkullOrange = 6 + +ShieldNone = 0 +ShieldWhiteYellow = 1 +ShieldWhiteBlue = 2 +ShieldBlue = 3 +ShieldYellow = 4 +ShieldBlueSharedExp = 5 +ShieldYellowSharedExp = 6 +ShieldBlueNoSharedExpBlink = 7 +ShieldYellowNoSharedExpBlink = 8 +ShieldBlueNoSharedExp = 9 +ShieldYellowNoSharedExp = 10 +ShieldGray = 11 + +EmblemNone = 0 +EmblemGreen = 1 +EmblemRed = 2 +EmblemBlue = 3 +EmblemMember = 4 +EmblemOther = 5 + +VipIconFirst = 0 +VipIconLast = 10 + +Directions = { + North = 0, + East = 1, + South = 2, + West = 3, + NorthEast = 4, + SouthEast = 5, + SouthWest = 6, + NorthWest = 7 +} + +Skill = { + Fist = 0, + Club = 1, + Sword = 2, + Axe = 3, + Distance = 4, + Shielding = 5, + Fishing = 6, + CriticalChance = 7, + CriticalDamage = 8, + LifeLeechChance = 9, + LifeLeechAmount = 10, + ManaLeechChance = 11, + ManaLeechAmount = 12 +} + +North = Directions.North +East = Directions.East +South = Directions.South +West = Directions.West +NorthEast = Directions.NorthEast +SouthEast = Directions.SouthEast +SouthWest = Directions.SouthWest +NorthWest = Directions.NorthWest + +FightOffensive = 1 +FightBalanced = 2 +FightDefensive = 3 + +DontChase = 0 +ChaseOpponent = 1 + +PVPWhiteDove = 0 +PVPWhiteHand = 1 +PVPYellowHand = 2 +PVPRedFist = 3 + +GameProtocolChecksum = 1 +GameAccountNames = 2 +GameChallengeOnLogin = 3 +GamePenalityOnDeath = 4 +GameNameOnNpcTrade = 5 +GameDoubleFreeCapacity = 6 +GameDoubleExperience = 7 +GameTotalCapacity = 8 +GameSkillsBase = 9 +GamePlayerRegenerationTime = 10 +GameChannelPlayerList = 11 +GamePlayerMounts = 12 +GameEnvironmentEffect = 13 +GameCreatureEmblems = 14 +GameItemAnimationPhase = 15 +GameMagicEffectU16 = 16 +GamePlayerMarket = 17 +GameSpritesU32 = 18 +GameTileAddThingWithStackpos = 19 +GameOfflineTrainingTime = 20 +GamePurseSlot = 21 +GameFormatCreatureName = 22 +GameSpellList = 23 +GameClientPing = 24 +GameExtendedClientPing = 25 +GameDoubleHealth = 28 +GameDoubleSkills = 29 +GameChangeMapAwareRange = 30 +GameMapMovePosition = 31 +GameAttackSeq = 32 +GameBlueNpcNameColor = 33 +GameDiagonalAnimatedText = 34 +GameLoginPending = 35 +GameNewSpeedLaw = 36 +GameForceFirstAutoWalkStep = 37 +GameMinimapRemove = 38 +GameDoubleShopSellAmount = 39 +GameContainerPagination = 40 +GameThingMarks = 41 +GameLooktypeU16 = 42 +GamePlayerStamina = 43 +GamePlayerAddons = 44 +GameMessageStatements = 45 +GameMessageLevel = 46 +GameNewFluids = 47 +GamePlayerStateU16 = 48 +GameNewOutfitProtocol = 49 +GamePVPMode = 50 +GameWritableDate = 51 +GameAdditionalVipInfo = 52 +GameBaseSkillU16 = 53 +GameCreatureIcons = 54 +GameHideNpcNames = 55 +GameSpritesAlphaChannel = 56 +GamePremiumExpiration = 57 +GameBrowseField = 58 +GameEnhancedAnimations = 59 +GameOGLInformation = 60 +GameMessageSizeCheck = 61 +GamePreviewState = 62 +GameLoginPacketEncryption = 63 +GameClientVersion = 64 +GameContentRevision = 65 +GameExperienceBonus = 66 +GameAuthenticator = 67 +GameUnjustifiedPoints = 68 +GameSessionKey = 69 +GameDeathType = 70 +GameIdleAnimations = 71 +GameKeepUnawareTiles = 72 +GameIngameStore = 73 +GameIngameStoreHighlights = 74 +GameIngameStoreServiceType = 75 +GameAdditionalSkills = 76 +GameDistanceEffectU16 = 77 +GamePrey = 78 +GameDoubleMagicLevel = 79 + +GameExtendedOpcode = 80 +GameMinimapLimitedToSingleFloor = 81 +GameSendWorldName = 82 + +GameDoubleLevel = 83 +GameDoubleSoul = 84 +GameDoublePlayerGoodsMoney = 85 +GameCreatureWalkthrough = 86 -- add Walkthrough for versions less than 854, unpass = msg->getU8(); in protocolgameparse.cpp +GameDoubleTradeMoney = 87 +GameSequencedPackets = 88 +GameTibia12Protocol = 89 + +GameNewWalking = 90 +GameSlowerManualWalking = 91 +GameItemTooltip = 93 + +GameBot = 95 +GameBiggerMapCache = 96 +GameForceLight = 97 +GameNoDebug = 98 +GameBotProtection = 99 + +GameCreatureDirectionPassable = 100 +GameFasterAnimations = 101 +GameCenteredOutfits = 102 +GameSendIdentifiers = 103 +GameWingsAndAura = 104 +GamePlayerStateU32 = 105 +GameOutfitShaders = 106 +GameForceAllowItemHotkeys = 107 +GameCountU16 = 108 +GameDrawAuraOnTop = 109 + +GamePacketSizeU32 = 110 +GamePacketCompression = 111 + +GameOldInformationBar = 112 +GameHealthInfoBackground = 113 +GameWingOffset = 114 +GameAuraFrontAndBack = 115 -- To use that: First layer is bottom/back, second (blend layer) is top/front + +GameMapDrawGroundFirst = 116 -- useful for big auras & wings +GameMapIgnoreCorpseCorrection = 117 +GameDontCacheFiles = 118 -- doesn't work with encryption and compression +GameBigAurasCenter = 119 -- Automatic negative offset for aura bigger than 32x32 +GameNewUpdateWalk = 120 -- Walk update rate dependant on FPS +GameNewCreatureStacking = 121 -- Ignore MAX_THINGS limit while adding to tile +GameCreaturesMana = 122 -- get mana from server for creatures other than Player +GameQuickLootFlags = 123 -- enables quick loot feature for all protocols +GameDontMergeAnimatedText = 124 +GameMissionId = 125 + +LastGameFeature = 130 + +TextColors = { + red = '#f55e5e', --'#c83200' + orange = '#f36500', --'#c87832' + yellow = '#ffff00', --'#e6c832' + green = '#00EB00', --'#3fbe32' + lightblue = '#5ff7f7', + blue = '#9f9dfd', + --blue1 = '#6e50dc', + --blue2 = '#3264c8', + --blue3 = '#0096c8', + white = '#ffffff', --'#bebebe' +} + +MessageModes = { + None = 0, + Say = 1, + Whisper = 2, + Yell = 3, + PrivateFrom = 4, + PrivateTo = 5, + ChannelManagement = 6, + Channel = 7, + ChannelHighlight = 8, + Spell = 9, + NpcFrom = 10, + NpcTo = 11, + GamemasterBroadcast = 12, + GamemasterChannel = 13, + GamemasterPrivateFrom = 14, + GamemasterPrivateTo = 15, + Login = 16, + Warning = 17, + Game = 18, + Failure = 19, + Look = 20, + DamageDealed = 21, + DamageReceived = 22, + Heal = 23, + Exp = 24, + DamageOthers = 25, + HealOthers = 26, + ExpOthers = 27, + Status = 28, + Loot = 29, + TradeNpc = 30, + Guild = 31, + PartyManagement = 32, + Party = 33, + BarkLow = 34, + BarkLoud = 35, + Report = 36, + HotkeyUse = 37, + TutorialHint = 38, + Thankyou = 39, + Market = 40, + Mana = 41, + BeyondLast = 42, + MonsterYell = 43, + MonsterSay = 44, + Red = 45, + Blue = 46, + RVRChannel = 47, + RVRAnswer = 48, + RVRContinue = 49, + GameHighlight = 50, + NpcFromStartBlock = 51, + Last = 52, + Invalid = 255, +} + +OTSERV_RSA = "1091201329673994292788609605089955415282375029027981291234687579" .. + "3726629149257644633073969600111060390723088861007265581882535850" .. + "3429057592827629436413108566029093628212635953836686562675849720" .. + "6207862794310902180176810615217550567108238764764442605581471797" .. + "07119674283982419152118103759076030616683978566631413" + +CIPSOFT_RSA = "1321277432058722840622950990822933849527763264961655079678763618" .. + "4334395343554449668205332383339435179772895415509701210392836078" .. + "6959821132214473291575712138800495033169914814069637740318278150" .. + "2907336840325241747827401343576296990629870233111328210165697754" .. + "88792221429527047321331896351555606801473202394175817" + +-- set to the latest Tibia.pic signature to make otclient compatible with official tibia +PIC_SIGNATURE = 0x56C5DDE7 + +OsTypes = { + Linux = 1, + Windows = 2, + Flash = 3, + OtclientLinux = 10, + OtclientWindows = 11, + OtclientMac = 12, +} + +PathFindResults = { + Ok = 0, + Position = 1, + Impossible = 2, + TooFar = 3, + NoWay = 4, +} + +PathFindFlags = { + AllowNullTiles = 1, + AllowCreatures = 2, + AllowNonPathable = 4, + AllowNonWalkable = 8, +} + +VipState = { + Offline = 0, + Online = 1, + Pending = 2, +} + +ExtendedIds = { + Activate = 0, + Locale = 1, + Ping = 2, + Sound = 3, + Game = 4, + Particles = 5, + MapShader = 6, + NeedsUpdate = 7 +} + +PreviewState = { + Default = 0, + Inactive = 1, + Active = 2 +} + +Blessings = { + None = 0, + Adventurer = 1, + SpiritualShielding = 2, + EmbraceOfTibia = 4, + FireOfSuns = 8, + WisdomOfSolitude = 16, + SparkOfPhoenix = 32 +} + +DeathType = { + Regular = 0, + Blessed = 1 +} + +ProductType = { + Other = 0, + NameChange = 1 +} + +StoreErrorType = { + NoError = -1, + PurchaseError = 0, + NetworkError = 1, + HistoryError = 2, + TransferError = 3, + Information = 4 +} + +StoreState = { + None = 0, + New = 1, + Sale = 2, + Timed = 3 +} + +AccountStatus = { + Ok = 0, + Frozen = 1, + Suspended = 2, +} + +SubscriptionStatus = { + Free = 0, + Premium = 1, +} + +ChannelEvent = { + Join = 0, + Leave = 1, + Invite = 2, + Exclude = 3, +} + +-- @} diff --git a/800OTClient/modules/gamelib/creature.lua b/800OTClient/modules/gamelib/creature.lua new file mode 100644 index 0000000..1568b57 --- /dev/null +++ b/800OTClient/modules/gamelib/creature.lua @@ -0,0 +1,176 @@ +-- @docclass Creature + +-- @docconsts @{ + +SkullNone = 0 +SkullYellow = 1 +SkullGreen = 2 +SkullWhite = 3 +SkullRed = 4 +SkullBlack = 5 +SkullOrange = 6 + +ShieldNone = 0 +ShieldWhiteYellow = 1 +ShieldWhiteBlue = 2 +ShieldBlue = 3 +ShieldYellow = 4 +ShieldBlueSharedExp = 5 +ShieldYellowSharedExp = 6 +ShieldBlueNoSharedExpBlink = 7 +ShieldYellowNoSharedExpBlink = 8 +ShieldBlueNoSharedExp = 9 +ShieldYellowNoSharedExp = 10 + +EmblemNone = 0 +EmblemGreen = 1 +EmblemRed = 2 +EmblemBlue = 3 + +NpcIconNone = 0 +NpcIconChat = 1 +NpcIconTrade = 2 +NpcIconQuest = 3 +NpcIconTradeQuest = 4 + +CreatureTypePlayer = 0 +CreatureTypeMonster = 1 +CreatureTypeNpc = 2 +CreatureTypeSummonOwn = 3 +CreatureTypeSummonOther = 4 + +-- @} + +function getNextSkullId(skullId) + if skullId == SkullRed or skullId == SkullBlack then + return SkullBlack + end + return SkullRed +end + +function getSkullImagePath(skullId) + local path + if skullId == SkullYellow then + path = '/images/game/skulls/skull_yellow' + elseif skullId == SkullGreen then + path = '/images/game/skulls/skull_green' + elseif skullId == SkullWhite then + path = '/images/game/skulls/skull_white' + elseif skullId == SkullRed then + path = '/images/game/skulls/skull_red' + elseif skullId == SkullBlack then + path = '/images/game/skulls/skull_black' + elseif skullId == SkullOrange then + path = '/images/game/skulls/skull_orange' + end + return path +end + +function getShieldImagePathAndBlink(shieldId) + local path, blink + if shieldId == ShieldWhiteYellow then + path, blink = '/images/game/shields/shield_yellow_white', false + elseif shieldId == ShieldWhiteBlue then + path, blink = '/images/game/shields/shield_blue_white', false + elseif shieldId == ShieldBlue then + path, blink = '/images/game/shields/shield_blue', false + elseif shieldId == ShieldYellow then + path, blink = '/images/game/shields/shield_yellow', false + elseif shieldId == ShieldBlueSharedExp then + path, blink = '/images/game/shields/shield_blue_shared', false + elseif shieldId == ShieldYellowSharedExp then + path, blink = '/images/game/shields/shield_yellow_shared', false + elseif shieldId == ShieldBlueNoSharedExpBlink then + path, blink = '/images/game/shields/shield_blue_not_shared', true + elseif shieldId == ShieldYellowNoSharedExpBlink then + path, blink = '/images/game/shields/shield_yellow_not_shared', true + elseif shieldId == ShieldBlueNoSharedExp then + path, blink = '/images/game/shields/shield_blue_not_shared', false + elseif shieldId == ShieldYellowNoSharedExp then + path, blink = '/images/game/shields/shield_yellow_not_shared', false + elseif shieldId == ShieldGray then + path, blink = '/images/game/shields/shield_gray', false + end + return path, blink +end + +function getEmblemImagePath(emblemId) + local path + if emblemId == EmblemGreen then + path = '/images/game/emblems/emblem_green' + elseif emblemId == EmblemRed then + path = '/images/game/emblems/emblem_red' + elseif emblemId == EmblemBlue then + path = '/images/game/emblems/emblem_blue' + elseif emblemId == EmblemMember then + path = '/images/game/emblems/emblem_member' + elseif emblemId == EmblemOther then + path = '/images/game/emblems/emblem_other' + end + return path +end + +function getTypeImagePath(creatureType) + local path + if creatureType == CreatureTypeSummonOwn then + path = '/images/game/creaturetype/summon_own' + elseif creatureType == CreatureTypeSummonOther then + path = '/images/game/creaturetype/summon_other' + end + return path +end + +function getIconImagePath(iconId) + local path + if iconId == NpcIconChat then + path = '/images/game/npcicons/icon_chat' + elseif iconId == NpcIconTrade then + path = '/images/game/npcicons/icon_trade' + elseif iconId == NpcIconQuest then + path = '/images/game/npcicons/icon_quest' + elseif iconId == NpcIconTradeQuest then + path = '/images/game/npcicons/icon_tradequest' + end + return path +end + +function Creature:onSkullChange(skullId) + local imagePath = getSkullImagePath(skullId) + if imagePath then + self:setSkullTexture(imagePath) + end +end + +function Creature:onShieldChange(shieldId) + local imagePath, blink = getShieldImagePathAndBlink(shieldId) + if imagePath then + self:setShieldTexture(imagePath, blink) + end +end + +function Creature:onEmblemChange(emblemId) + local imagePath = getEmblemImagePath(emblemId) + if imagePath then + self:setEmblemTexture(imagePath) + end +end + +function Creature:onTypeChange(typeId) + local imagePath = getTypeImagePath(typeId) + if imagePath then + self:setTypeTexture(imagePath) + end +end + +function Creature:onIconChange(iconId) + local imagePath = getIconImagePath(iconId) + if imagePath then + self:setIconTexture(imagePath) + end +end + +function Creature:setOutfitShader(shader) + local outfit = self:getOutfit() + outfit.shader = shader + self:setOutfit(outfit) +end diff --git a/800OTClient/modules/gamelib/game.lua b/800OTClient/modules/gamelib/game.lua new file mode 100644 index 0000000..816685c --- /dev/null +++ b/800OTClient/modules/gamelib/game.lua @@ -0,0 +1,118 @@ +function g_game.getRsa() + return G.currentRsa +end + +function g_game.findPlayerItem(itemId, subType) + local localPlayer = g_game.getLocalPlayer() + if localPlayer then + for slot = InventorySlotFirst, InventorySlotLast do + local item = localPlayer:getInventoryItem(slot) + if item and item:getId() == itemId and (subType == -1 or item:getSubType() == subType) then + return item + end + end + end + + return g_game.findItemInContainers(itemId, subType) +end + +function g_game.chooseRsa(host) + if G.currentRsa ~= CIPSOFT_RSA and G.currentRsa ~= OTSERV_RSA then return end + if host:ends('.tibia.com') or host:ends('.cipsoft.com') then + g_game.setRsa(CIPSOFT_RSA) + + if g_app.getOs() == 'windows' then + g_game.setCustomOs(OsTypes.Windows) + else + g_game.setCustomOs(OsTypes.Linux) + end + else + if G.currentRsa == CIPSOFT_RSA then + g_game.setCustomOs(-1) + end + g_game.setRsa(OTSERV_RSA) + end + + -- Hack fix to resolve some 760 login issues + if g_game.getClientVersion() <= 760 then + g_game.setCustomOs(2) + end +end + +function g_game.setRsa(rsa, e) + e = e or '65537' + g_crypt.rsaSetPublicKey(rsa, e) + G.currentRsa = rsa +end + +function g_game.isOfficialTibia() + return G.currentRsa == CIPSOFT_RSA +end + +function g_game.getSupportedClients() + return { + 740, 741, 750, 760, 770, 772, + 780, 781, 782, 790, 792, + + 800, 810, 811, 820, 821, 822, + 830, 831, 840, 842, 850, 853, + 854, 855, 857, 860, 861, 862, + 870, 871, + + 900, 910, 920, 931, 940, 943, + 944, 951, 952, 953, 954, 960, + 961, 963, 970, 971, 972, 973, + 980, 981, 982, 983, 984, 985, + 986, + + 1000, 1001, 1002, 1010, 1011, + 1012, 1013, 1020, 1021, 1022, + 1030, 1031, 1032, 1033, 1034, + 1035, 1036, 1037, 1038, 1039, + 1040, 1041, 1050, 1051, 1052, + 1053, 1054, 1055, 1056, 1057, + 1058, 1059, 1060, 1061, 1062, + 1063, 1064, 1070, 1071, 1072, + 1073, 1074, 1075, 1076, 1080, + 1081, 1082, 1090, 1091, 1092, + 1093, 1094, 1095, 1096, 1097, + 1098, 1099 + } +end + +-- The client version and protocol version where +-- unsynchronized for some releases, not sure if this +-- will be the normal standard. + +-- Client Version: Publicly given version when +-- downloading Cipsoft client. + +-- Protocol Version: Previously was the same as +-- the client version, but was unsychronized in some +-- releases, now it needs to be verified and added here +-- if it does not match the client version. + +-- Reason for defining both: The server now requires a +-- Client version and Protocol version from the client. + +-- Important: Use getClientVersion for specific protocol +-- features to ensure we are using the proper version. + +function g_game.getClientProtocolVersion(client) + local clients = { + [980] = 971, + [981] = 973, + [982] = 974, + [983] = 975, + [984] = 976, + [985] = 977, + [986] = 978, + [1001] = 979, + [1002] = 980 + } + return clients[client] or client +end + +if not G.currentRsa then + g_game.setRsa(OTSERV_RSA) +end diff --git a/800OTClient/modules/gamelib/gamelib.otmod b/800OTClient/modules/gamelib/gamelib.otmod new file mode 100644 index 0000000..ab71364 --- /dev/null +++ b/800OTClient/modules/gamelib/gamelib.otmod @@ -0,0 +1,23 @@ +Module + name: gamelib + description: Contains game related classes + author: OTClient team + website: https://github.com/edubart/otclient + + @onLoad: | + dofile 'const' + dofile 'util' + dofile 'protocol' + dofile 'protocollogin' + dofile 'protocolgame' + dofile 'position' + dofile 'game' + + dofile 'creature' + dofile 'player' + dofile 'market' + dofile 'textmessages' + dofile 'thing' + dofile 'spells' + + dofiles 'ui' diff --git a/800OTClient/modules/gamelib/market.lua b/800OTClient/modules/gamelib/market.lua new file mode 100644 index 0000000..e88e5f8 --- /dev/null +++ b/800OTClient/modules/gamelib/market.lua @@ -0,0 +1,202 @@ +MarketMaxAmount = 2000 +MarketMaxAmountStackable = 64000 +MarketMaxPrice = 999999999 +MarketMaxOffers = 100 + +MarketAction = { + Buy = 0, + Sell = 1 +} + +MarketRequest = { + MyOffers = 0xFFFE, + MyHistory = 0xFFFF +} + +MarketOfferState = { + Active = 0, + Cancelled = 1, + Expired = 2, + Accepted = 3, + AcceptedEx = 255 +} + +MarketCategory = { + All = 0, + Armors = 1, + Amulets = 2, + Boots = 3, + Containers = 4, + Decoration = 5, + Food = 6, + HelmetsHats = 7, + Legs = 8, + Others = 9, + Potions = 10, + Rings = 11, + Runes = 12, + Shields = 13, + Tools = 14, + Valuables = 15, + Ammunition = 16, + Axes = 17, + Clubs = 18, + DistanceWeapons = 19, + Swords = 20, + WandsRods = 21, + PremiumScrolls = 22, + TibiaCoins = 23, + CreatureProducs = 24, + Unknown1 = 25, + Unknown2 = 26, + StashRetrieve = 27, + Unknown3 = 28, + Unknown4 = 29, + Gold = 30, + Unassigned = 31, + MetaWeapons = 255 +} + +MarketCategory.First = MarketCategory.Armors +MarketCategory.Last = MarketCategory.Unassigned + +MarketCategoryWeapons = { + [MarketCategory.Ammunition] = { slots = {255} }, + [MarketCategory.Axes] = { slots = {255, InventorySlotOther, InventorySlotLeft} }, + [MarketCategory.Clubs] = { slots = {255, InventorySlotOther, InventorySlotLeft} }, + [MarketCategory.DistanceWeapons] = { slots = {255, InventorySlotOther, InventorySlotLeft} }, + [MarketCategory.Swords] = { slots = {255, InventorySlotOther, InventorySlotLeft} }, + [MarketCategory.WandsRods] = { slots = {255, InventorySlotOther, InventorySlotLeft} } +} + +MarketCategoryStrings = { + [0] = 'All', + [1] = 'Armors', + [2] = 'Amulets', + [3] = 'Boots', + [4] = 'Containers', + [5] = 'Decoration', + [6] = 'Food', + [7] = 'Helmets and Hats', + [8] = 'Legs', + [9] = 'Others', + [10] = 'Potions', + [11] = 'Rings', + [12] = 'Runes', + [13] = 'Shields', + [14] = 'Tools', + [15] = 'Valuables', + [16] = 'Ammunition', + [17] = 'Axes', + [18] = 'Clubs', + [19] = 'Distance Weapons', + [20] = 'Swords', + [21] = 'Wands and Rods', + [22] = 'Premium Scrolls', + [23] = 'Tibia Coins', + [24] = 'Creature Products', + [25] = 'Unknown 1', + [26] = 'Unknown 2', + [27] = 'Stash Retrieve', + [28] = 'Unknown 3', + [29] = 'Unknown 4', + [30] = 'Gold', + [31] = 'Unassigned', + [255] = 'Weapons' +} + +function getMarketCategoryName(id) + if table.haskey(MarketCategoryStrings, id) then + return MarketCategoryStrings[id] + end +end + +function getMarketCategoryId(name) + local id = table.find(MarketCategoryStrings, name) + if id then + return id + end +end + +MarketItemDescription = { + Armor = 1, + Attack = 2, + Container = 3, + Defense = 4, + General = 5, + DecayTime = 6, + Combat = 7, + MinLevel = 8, + MinMagicLevel = 9, + Vocation = 10, + Rune = 11, + Ability = 12, + Charges = 13, + WeaponName = 14, + Weight = 15, + Imbuements = 16 +} + +MarketItemDescription.First = MarketItemDescription.Armor +MarketItemDescription.Last = MarketItemDescription.Weight + +MarketItemDescriptionStrings = { + [1] = 'Armor', + [2] = 'Attack', + [3] = 'Container', + [4] = 'Defense', + [5] = 'Description', + [6] = 'Use Time', + [7] = 'Combat', + [8] = 'Min Level', + [9] = 'Min Magic Level', + [10] = 'Vocation', + [11] = 'Rune', + [12] = 'Ability', + [13] = 'Charges', + [14] = 'Weapon Type', + [15] = 'Weight', + [16] = 'Imbuements' +} + +function getMarketDescriptionName(id) + if table.haskey(MarketItemDescriptionStrings, id) then + return MarketItemDescriptionStrings[id] + end +end + +function getMarketDescriptionId(name) + local id = table.find(MarketItemDescriptionStrings, name) + if id then + return id + end +end + +MarketSlotFilters = { + [InventorySlotOther] = "Two-Handed", + [InventorySlotLeft] = "One-Handed", + [255] = "Any" +} + +MarketFilters = { + Vocation = 1, + Level = 2, + Depot = 3, + SearchAll = 4 +} + +MarketFilters.First = MarketFilters.Vocation +MarketFilters.Last = MarketFilters.Depot + +function getMarketSlotFilterId(name) + local id = table.find(MarketSlotFilters, name) + if id then + return id + end +end + +function getMarketSlotFilterName(id) + if table.haskey(MarketSlotFilters, id) then + return MarketSlotFilters[id] + end +end diff --git a/800OTClient/modules/gamelib/player.lua b/800OTClient/modules/gamelib/player.lua new file mode 100644 index 0000000..1ac7291 --- /dev/null +++ b/800OTClient/modules/gamelib/player.lua @@ -0,0 +1,151 @@ +-- @docclass Player + +PlayerStates = { + None = 0, + Poison = 1, + Burn = 2, + Energy = 4, + Drunk = 8, + ManaShield = 16, + Paralyze = 32, + Haste = 64, + Swords = 128, + Drowning = 256, + Freezing = 512, + Dazzled = 1024, + Cursed = 2048, + PartyBuff = 4096, + PzBlock = 8192, + Pz = 16384, + Bleeding = 32768, + Hungry = 65536 +} + +InventorySlotOther = 0 +InventorySlotHead = 1 +InventorySlotNeck = 2 +InventorySlotBack = 3 +InventorySlotBody = 4 +InventorySlotRight = 5 +InventorySlotLeft = 6 +InventorySlotLeg = 7 +InventorySlotFeet = 8 +InventorySlotFinger = 9 +InventorySlotAmmo = 10 +InventorySlotPurse = 11 + +InventorySlotFirst = 1 +InventorySlotLast = 10 + +function Player:isPartyLeader() + local shield = self:getShield() + return (shield == ShieldWhiteYellow or + shield == ShieldYellow or + shield == ShieldYellowSharedExp or + shield == ShieldYellowNoSharedExpBlink or + shield == ShieldYellowNoSharedExp) +end + +function Player:isPartyMember() + local shield = self:getShield() + return (shield == ShieldWhiteYellow or + shield == ShieldYellow or + shield == ShieldYellowSharedExp or + shield == ShieldYellowNoSharedExpBlink or + shield == ShieldYellowNoSharedExp or + shield == ShieldBlueSharedExp or + shield == ShieldBlueNoSharedExpBlink or + shield == ShieldBlueNoSharedExp or + shield == ShieldBlue) +end + +function Player:isPartySharedExperienceActive() + local shield = self:getShield() + return (shield == ShieldYellowSharedExp or + shield == ShieldYellowNoSharedExpBlink or + shield == ShieldYellowNoSharedExp or + shield == ShieldBlueSharedExp or + shield == ShieldBlueNoSharedExpBlink or + shield == ShieldBlueNoSharedExp) +end + +function Player:hasVip(creatureName) + for id, vip in pairs(g_game.getVips()) do + if (vip[1] == creatureName) then return true end + end + return false +end + +function Player:isMounted() + local outfit = self:getOutfit() + return outfit.mount ~= nil and outfit.mount > 0 +end + +function Player:toggleMount() + if g_game.getFeature(GamePlayerMounts) then + g_game.mount(not self:isMounted()) + end +end + +function Player:mount() + if g_game.getFeature(GamePlayerMounts) then + g_game.mount(true) + end +end + +function Player:dismount() + if g_game.getFeature(GamePlayerMounts) then + g_game.mount(false) + end +end + +function Player:getItem(itemId, subType) + return g_game.findPlayerItem(itemId, subType or -1) +end + +function Player:getItems(itemId, subType) + local subType = subType or -1 + + local items = {} + for i=InventorySlotFirst,InventorySlotLast do + local item = self:getInventoryItem(i) + if item and item:getId() == itemId and (subType == -1 or item:getSubType() == subType) then + table.insert(items, item) + end + end + + for i, container in pairs(g_game.getContainers()) do + for j, item in pairs(container:getItems()) do + if item:getId() == itemId and (subType == -1 or item:getSubType() == subType) then + item.container = container + table.insert(items, item) + end + end + end + return items +end + +function Player:getItemsCount(itemId) + local items, count = self:getItems(itemId), 0 + for i=1,#items do + count = count + items[i]:getCount() + end + return count +end + +function Player:hasState(state, states) + if not states then + states = self:getStates() + end + + for i = 1, 32 do + local pow = math.pow(2, i-1) + if pow > states then break end + + local states = bit32.band(states, pow) + if states == state then + return true + end + end + return false +end diff --git a/800OTClient/modules/gamelib/position.lua b/800OTClient/modules/gamelib/position.lua new file mode 100644 index 0000000..b41a0fa --- /dev/null +++ b/800OTClient/modules/gamelib/position.lua @@ -0,0 +1,37 @@ +Position = {} + +function Position.equals(pos1, pos2) + return pos1.x == pos2.x and pos1.y == pos2.y and pos1.z == pos2.z +end + +function Position.greaterThan(pos1, pos2, orEqualTo) + if orEqualTo then + return pos1.x >= pos2.x or pos1.y >= pos2.y or pos1.z >= pos2.z + else + return pos1.x > pos2.x or pos1.y > pos2.y or pos1.z > pos2.z + end +end + +function Position.lessThan(pos1, pos2, orEqualTo) + if orEqualTo then + return pos1.x <= pos2.x or pos1.y <= pos2.y or pos1.z <= pos2.z + else + return pos1.x < pos2.x or pos1.y < pos2.y or pos1.z < pos2.z + end +end + +function Position.isInRange(pos1, pos2, xRange, yRange) + return math.abs(pos1.x-pos2.x) <= xRange and math.abs(pos1.y-pos2.y) <= yRange and pos1.z == pos2.z; +end + +function Position.isValid(pos) + return not (pos.x == 65535 and pos.y == 65535 and pos.z == 255) +end + +function Position.distance(pos1, pos2) + return math.sqrt(math.pow((pos2.x - pos1.x), 2) + math.pow((pos2.y - pos1.y), 2)) +end + +function Position.manhattanDistance(pos1, pos2) + return math.abs(pos2.x - pos1.x) + math.abs(pos2.y - pos1.y) +end \ No newline at end of file diff --git a/800OTClient/modules/gamelib/protocol.lua b/800OTClient/modules/gamelib/protocol.lua new file mode 100644 index 0000000..205a938 --- /dev/null +++ b/800OTClient/modules/gamelib/protocol.lua @@ -0,0 +1,206 @@ +GameServerOpcodes = { + GameServerInitGame = 10, + GameServerGMActions = 11, + GameServerEnterGame = 15, + GameServerLoginError = 20, + GameServerLoginAdvice = 21, + GameServerLoginWait = 22, + GameServerAddCreature = 23, + GameServerPingBack = 29, + GameServerPing = 30, + GameServerChallenge = 31, + GameServerDeath = 40, + + -- all in game opcodes must be greater than 50 + GameServerFirstGameOpcode = 50, + + -- otclient ONLY + GameServerExtendedOpcode = 50, + + -- NOTE: add any custom opcodes in this range + -- 51 - 99 + + -- original tibia ONLY + GameServerFullMap = 100, + GameServerMapTopRow = 101, + GameServerMapRightRow = 102, + GameServerMapBottomRow = 103, + GameServerMapLeftRow = 104, + GameServerUpdateTile = 105, + GameServerCreateOnMap = 106, + GameServerChangeOnMap = 107, + GameServerDeleteOnMap = 108, + GameServerMoveCreature = 109, + GameServerOpenContainer = 110, + GameServerCloseContainer = 111, + GameServerCreateContainer = 112, + GameServerChangeInContainer = 113, + GameServerDeleteInContainer = 114, + GameServerSetInventory = 120, + GameServerDeleteInventory = 121, + GameServerOpenNpcTrade = 122, + GameServerPlayerGoods = 123, + GameServerCloseNpcTrade = 124, + GameServerOwnTrade = 125, + GameServerCounterTrade = 126, + GameServerCloseTrade = 127, + GameServerAmbient = 130, + GameServerGraphicalEffect = 131, + GameServerTextEffect = 132, + GameServerMissleEffect = 133, + GameServerMarkCreature = 134, + GameServerTrappers = 135, + GameServerCreatureHealth = 140, + GameServerCreatureLight = 141, + GameServerCreatureOutfit = 142, + GameServerCreatureSpeed = 143, + GameServerCreatureSkull = 144, + GameServerCreatureParty = 145, + GameServerCreatureUnpass = 146, + GameServerEditText = 150, + GameServerEditList = 151, + GameServerPlayerDataBasic = 159, -- 910 + GameServerPlayerData = 160, + GameServerPlayerSkills = 161, + GameServerPlayerState = 162, + GameServerClearTarget = 163, + GameServerSpellDelay = 164, -- 870 + GameServerSpellGroupDelay = 165, -- 870 + GameServerMultiUseDelay = 166, -- 870 + GameServerTalk = 170, + GameServerChannels = 171, + GameServerOpenChannel = 172, + GameServerOpenPrivateChannel = 173, + GameServerRuleViolationChannel = 174, + GameServerRuleViolationRemove = 175, + GameServerRuleViolationCancel = 176, + GameServerRuleViolationLock = 177, + GameServerOpenOwnChannel = 178, + GameServerCloseChannel = 179, + GameServerTextMessage = 180, + GameServerCancelWalk = 181, + GameServerWalkWait = 182, + GameServerFloorChangeUp = 190, + GameServerFloorChangeDown = 191, + GameServerChooseOutfit = 200, + GameServerVipAdd = 210, + GameServerVipLogin = 211, + GameServerVipLogout = 212, + GameServerTutorialHint = 220, + GameServerAutomapFlag = 221, + GameServerCoinBalance = 223, -- 1080 + GameServerStoreError = 224, -- 1080 + GameServerRequestPurchaseData = 225, -- 1080 + GameServerQuestLog = 240, + GameServerQuestLine = 241, + GameServerCoinBalanceUpdating = 242, -- 1080 + GameServerChannelEvent = 243, -- 910 + GameServerItemInfo = 244, -- 910 + GameServerPlayerInventory = 245, -- 910 + GameServerMarketEnter = 246, -- 944 + GameServerMarketLeave = 247, -- 944 + GameServerMarketDetail = 248, -- 944 + GameServerMarketBrowse = 249, -- 944 + GameServerShowModalDialog = 250, -- 960 + GameServerStore = 251, -- 1080 + GameServerStoreOffers = 252, -- 1080 + GameServerStoreTransactionHistory = 253, -- 1080 + GameServerStoreCompletePurchase = 254 -- 1080 +} + +ClientOpcodes = { + ClientEnterAccount = 1, + ClientEnterGame = 10, + ClientLeaveGame = 20, + ClientPing = 29, + ClientPingBack = 30, + + -- all in game opcodes must be equal or greater than 50 + ClientFirstGameOpcode = 50, + + -- otclient ONLY + ClientExtendedOpcode = 50, + + -- NOTE: add any custom opcodes in this range + -- 51 - 99 + + -- original tibia ONLY + ClientAutoWalk = 100, + ClientWalkNorth = 101, + ClientWalkEast = 102, + ClientWalkSouth = 103, + ClientWalkWest = 104, + ClientStop = 105, + ClientWalkNorthEast = 106, + ClientWalkSouthEast = 107, + ClientWalkSouthWest = 108, + ClientWalkNorthWest = 109, + ClientTurnNorth = 111, + ClientTurnEast = 112, + ClientTurnSouth = 113, + ClientTurnWest = 114, + ClientEquipItem = 119, -- 910 + ClientMove = 120, + ClientInspectNpcTrade = 121, + ClientBuyItem = 122, + ClientSellItem = 123, + ClientCloseNpcTrade = 124, + ClientRequestTrade = 125, + ClientInspectTrade = 126, + ClientAcceptTrade = 127, + ClientRejectTrade = 128, + ClientUseItem = 130, + ClientUseItemWith = 131, + ClientUseOnCreature = 132, + ClientRotateItem = 133, + ClientCloseContainer = 135, + ClientUpContainer = 136, + ClientEditText = 137, + ClientEditList = 138, + ClientLook = 140, + ClientTalk = 150, + ClientRequestChannels = 151, + ClientJoinChannel = 152, + ClientLeaveChannel = 153, + ClientOpenPrivateChannel = 154, + ClientCloseNpcChannel = 158, + ClientChangeFightModes = 160, + ClientAttack = 161, + ClientFollow = 162, + ClientInviteToParty = 163, + ClientJoinParty = 164, + ClientRevokeInvitation = 165, + ClientPassLeadership = 166, + ClientLeaveParty = 167, + ClientShareExperience = 168, + ClientDisbandParty = 169, + ClientOpenOwnChannel = 170, + ClientInviteToOwnChannel = 171, + ClientExcludeFromOwnChannel = 172, + ClientCancelAttackAndFollow = 190, + ClientRefreshContainer = 202, + ClientRequestOutfit = 210, + ClientChangeOutfit = 211, + ClientMount = 212, -- 870 + ClientAddVip = 220, + ClientRemoveVip = 221, + ClientBugReport = 230, + ClientRuleViolation = 231, + ClientDebugReport = 232, + ClientTransferCoins = 239, -- 1080 + ClientRequestQuestLog = 240, + ClientRequestQuestLine = 241, + ClientNewRuleViolation = 242, -- 910 + ClientRequestItemInfo = 243, -- 910 + ClientMarketLeave = 244, -- 944 + ClientMarketBrowse = 245, -- 944 + ClientMarketCreate = 246, -- 944 + ClientMarketCancel = 247, -- 944 + ClientMarketAccept = 248, -- 944 + ClientAnswerModalDialog = 249, -- 960 + ClientOpenStore = 250, -- 1080 + ClientRequestStoreOffers = 251, -- 1080 + ClientBuyStoreOffer = 252, -- 1080 + ClientOpenTransactionHistory = 253, -- 1080 + ClientRequestTransactionHistory = 254 -- 1080 +} diff --git a/800OTClient/modules/gamelib/protocolgame.lua b/800OTClient/modules/gamelib/protocolgame.lua new file mode 100644 index 0000000..e340aaa --- /dev/null +++ b/800OTClient/modules/gamelib/protocolgame.lua @@ -0,0 +1,138 @@ +local opcodeCallbacks = {} +local extendedCallbacks = {} +local extendedJSONCallbacks = {} +local extendedJSONData = {} +local maxPacketSize = 65000 + +function ProtocolGame:onOpcode(opcode, msg) + for i, callback in pairs(opcodeCallbacks) do + if i == opcode then + callback(self, msg) + return true + end + end + return false +end + +function ProtocolGame:onExtendedOpcode(opcode, buffer) + local callback = extendedCallbacks[opcode] + if callback then + callback(self, opcode, buffer) + end + + callback = extendedJSONCallbacks[opcode] + if callback then + local status = buffer:sub(1,1) -- O - just one message, S - start, P - part, E - end + local data = buffer:sub(2) + if status ~= "E" and status ~= "P" then + extendedJSONData[opcode] = "" + end + if status ~= "S" and status ~= "P" and status ~= "E" then + extendedJSONData[opcode] = buffer + else + extendedJSONData[opcode] = extendedJSONData[opcode] .. data + end + if status ~= "S" and status ~= "P" then + local json_status, json_data = pcall(function() return json.decode(extendedJSONData[opcode]) end) + extendedJSONData[opcode] = nil + if not json_status then + error("Invalid data in extended JSON opcode (" .. json_status .. "): " .. json_data) + return + end + callback(self, opcode, json_data) + end + end +end + +function ProtocolGame.registerOpcode(opcode, callback) + if opcodeCallbacks[opcode] then + error('opcode ' .. opcode .. ' already registered will be overriden') + end + + opcodeCallbacks[opcode] = callback +end + +function ProtocolGame.unregisterOpcode(opcode) + opcodeCallbacks[opcode] = nil +end + +function ProtocolGame.registerExtendedOpcode(opcode, callback) + if not callback or type(callback) ~= 'function' then + error('Invalid callback.') + end + + if opcode < 0 or opcode > 255 then + error('Invalid opcode. Range: 0-255') + end + + if extendedCallbacks[opcode] then + error('Opcode is already taken.') + end + + extendedCallbacks[opcode] = callback +end + +function ProtocolGame.unregisterExtendedOpcode(opcode) + if opcode < 0 or opcode > 255 then + error('Invalid opcode. Range: 0-255') + end + + if not extendedCallbacks[opcode] then + error('Opcode is not registered.') + end + + extendedCallbacks[opcode] = nil +end + +function ProtocolGame.registerExtendedJSONOpcode(opcode, callback) + if not callback or type(callback) ~= 'function' then + error('Invalid callback.') + end + + if opcode < 0 or opcode > 255 then + error('Invalid opcode. Range: 0-255') + end + + if extendedJSONCallbacks[opcode] then + error('Opcode is already taken.') + end + + extendedJSONCallbacks[opcode] = callback +end + +function ProtocolGame.unregisterExtendedJSONOpcode(opcode) + if opcode < 0 or opcode > 255 then + error('Invalid opcode. Range: 0-255') + end + + if not extendedJSONCallbacks[opcode] then + error('Opcode is not registered.') + end + + extendedJSONCallbacks[opcode] = nil +end + +function ProtocolGame:sendExtendedJSONOpcode(opcode, data) + if opcode < 0 or opcode > 255 then + error('Invalid opcode. Range: 0-255') + end + + if type(data) ~= "table" then + error('Invalid data type, should be table') + end + + local buffer = json.encode(data) + local s = {} + for i=1, #buffer, maxPacketSize do + s[#s+1] = buffer:sub(i,i+maxPacketSize - 1) + end + if #s == 1 then + self:sendExtendedOpcode(opcode, s[1]) + return + end + self:sendExtendedOpcode(opcode, "S" .. s[1]) + for i=2,#s - 1 do + self:sendExtendedOpcode(opcode, "P" .. s[i]) + end + self:sendExtendedOpcode(opcode, "E" .. s[#s]) +end diff --git a/800OTClient/modules/gamelib/protocollogin.lua b/800OTClient/modules/gamelib/protocollogin.lua new file mode 100644 index 0000000..bc289cf --- /dev/null +++ b/800OTClient/modules/gamelib/protocollogin.lua @@ -0,0 +1,300 @@ +-- @docclass +ProtocolLogin = extends(Protocol, "ProtocolLogin") + +LoginServerError = 10 +LoginServerTokenSuccess = 12 +LoginServerTokenError = 13 +LoginServerUpdate = 17 +LoginServerMotd = 20 +LoginServerUpdateNeeded = 30 +LoginServerSessionKey = 40 +LoginServerCharacterList = 100 +LoginServerExtendedCharacterList = 101 +LoginServerProxyList = 110 + +-- Since 10.76 +LoginServerRetry = 10 +LoginServerErrorNew = 11 + +function ProtocolLogin:login(host, port, accountName, accountPassword, authenticatorToken, stayLogged) + if string.len(host) == 0 or port == nil or port == 0 then + signalcall(self.onLoginError, self, tr("You must enter a valid server address and port.")) + return + end + + self.accountName = accountName + self.accountPassword = accountPassword + self.authenticatorToken = authenticatorToken + self.stayLogged = stayLogged + self.connectCallback = self.sendLoginPacket + + self:connect(host, port) +end + +function ProtocolLogin:cancelLogin() + self:disconnect() +end + +function ProtocolLogin:sendLoginPacket() + local msg = OutputMessage.create() + msg:addU8(ClientOpcodes.ClientEnterAccount) + msg:addU16(g_game.getOs()) + if g_game.getCustomProtocolVersion() > 0 then + msg:addU16(g_game.getCustomProtocolVersion()) + else + msg:addU16(g_game.getProtocolVersion()) + end + + if g_game.getFeature(GameClientVersion) then + msg:addU32(g_game.getClientVersion()) + end + + if g_game.getFeature(GameContentRevision) then + msg:addU16(g_things.getContentRevision()) + msg:addU16(0) + else + msg:addU32(g_things.getDatSignature()) + end + msg:addU32(g_sprites.getSprSignature()) + msg:addU32(PIC_SIGNATURE) + + if g_game.getFeature(GamePreviewState) then + msg:addU8(0) + end + + local offset = msg:getMessageSize() + if g_game.getFeature(GameLoginPacketEncryption) then + -- first RSA byte must be 0 + msg:addU8(0) + + -- xtea key + self:generateXteaKey() + local xteaKey = self:getXteaKey() + msg:addU32(xteaKey[1]) + msg:addU32(xteaKey[2]) + msg:addU32(xteaKey[3]) + msg:addU32(xteaKey[4]) + end + + if g_game.getFeature(GameAccountNames) then + msg:addString(self.accountName) + else + msg:addU32(tonumber(self.accountName)) + msg:addU32(tonumber(self.accountName)) + end + + msg:addString(self.accountPassword) + + if self.getLoginExtendedData then + local data = self:getLoginExtendedData() + msg:addString(data) + else + msg:addString("OTCv8") + local version = g_app.getVersion():split(" ")[1]:gsub("%.", "") + if version:len() == 2 then + version = version .. "0" + end + msg:addU16(tonumber(version)) + end + + local paddingBytes = g_crypt.rsaGetSize() - (msg:getMessageSize() - offset) + assert(paddingBytes >= 0) + for i = 1, paddingBytes do + msg:addU8(math.random(0, 0xff)) + end + + if g_game.getFeature(GameLoginPacketEncryption) then + msg:encryptRsa() + end + + if g_game.getFeature(GameOGLInformation) then + msg:addU8(1) --unknown + msg:addU8(1) --unknown + + if g_game.getProtocolVersion() >= 1072 then + msg:addString(string.format('%s %s', g_graphics.getVendor(), g_graphics.getRenderer())) + else + msg:addString(g_graphics.getRenderer()) + end + msg:addString(g_graphics.getVersion()) + end + + -- add RSA encrypted auth token + if g_game.getFeature(GameAuthenticator) then + offset = msg:getMessageSize() + + -- first RSA byte must be 0 + msg:addU8(0) + msg:addString(self.authenticatorToken) + + if g_game.getFeature(GameSessionKey) then + msg:addU8(booleantonumber(self.stayLogged)) + end + + paddingBytes = g_crypt.rsaGetSize() - (msg:getMessageSize() - offset) + assert(paddingBytes >= 0) + for i = 1, paddingBytes do + msg:addU8(math.random(0, 0xff)) + end + + msg:encryptRsa() + end + + if g_game.getFeature(GamePacketSizeU32) then + self:enableBigPackets() + end + + if g_game.getFeature(GameProtocolChecksum) then + self:enableChecksum() + end + + self:send(msg) + if g_game.getFeature(GameLoginPacketEncryption) then + self:enableXteaEncryption() + end + self:recv() +end + +function ProtocolLogin:onConnect() + self.gotConnection = true + self:connectCallback() + self.connectCallback = nil +end + +function ProtocolLogin:onRecv(msg) + while not msg:eof() do + local opcode = msg:getU8() + if opcode == LoginServerErrorNew then + self:parseError(msg) + elseif opcode == LoginServerError then + self:parseError(msg) + elseif opcode == LoginServerMotd then + self:parseMotd(msg) + elseif opcode == LoginServerUpdateNeeded then + signalcall(self.onLoginError, self, tr("Client needs update.")) + elseif opcode == LoginServerTokenSuccess then + local unknown = msg:getU8() + elseif opcode == LoginServerTokenError then + -- TODO: prompt for token here + local unknown = msg:getU8() + signalcall(self.onLoginError, self, tr("Invalid authentification token.")) + elseif opcode == LoginServerCharacterList then + self:parseCharacterList(msg) + elseif opcode == LoginServerExtendedCharacterList then + self:parseExtendedCharacterList(msg) + elseif opcode == LoginServerUpdate then + local signature = msg:getString() + signalcall(self.onUpdateNeeded, self, signature) + elseif opcode == LoginServerSessionKey then + self:parseSessionKey(msg) + elseif opcode == LoginServerProxyList then + local proxies = {} + local proxiesCount = msg:getU8() + for i=1, proxiesCount do + local host = msg:getString() + local port = msg:getU16() + local priority = msg:getU16() + table.insert(proxies, {host=host, port=port, priority=priority}) + end + signalcall(self.onProxyList, self, proxies) + else + self:parseOpcode(opcode, msg) + end + end + self:disconnect() +end + +function ProtocolLogin:parseError(msg) + local errorMessage = msg:getString() + signalcall(self.onLoginError, self, errorMessage) +end + +function ProtocolLogin:parseMotd(msg) + local motd = msg:getString() + signalcall(self.onMotd, self, motd) +end + +function ProtocolLogin:parseSessionKey(msg) + local sessionKey = msg:getString() + signalcall(self.onSessionKey, self, sessionKey) +end + +function ProtocolLogin:parseCharacterList(msg) + local characters = {} + + if g_game.getProtocolVersion() > 1010 then + local worlds = {} + + local worldsCount = msg:getU8() + for i=1, worldsCount do + local world = {} + local worldId = msg:getU8() + world.worldName = msg:getString() + world.worldIp = msg:getString() + world.worldPort = msg:getU16() + world.previewState = msg:getU8() + worlds[worldId] = world + end + + local charactersCount = msg:getU8() + for i=1, charactersCount do + local character = {} + local worldId = msg:getU8() + character.name = msg:getString() + character.worldName = worlds[worldId].worldName + character.worldIp = worlds[worldId].worldIp + character.worldPort = worlds[worldId].worldPort + character.previewState = worlds[worldId].previewState + characters[i] = character + end + + else + local charactersCount = msg:getU8() + for i=1,charactersCount do + local character = {} + character.name = msg:getString() + character.worldName = msg:getString() + character.worldIp = iptostring(msg:getU32()) + character.worldPort = msg:getU16() + + if g_game.getFeature(GamePreviewState) then + character.previewState = msg:getU8() + end + + characters[i] = character + end + end + + local account = {} + if g_game.getProtocolVersion() > 1077 then + account.status = msg:getU8() + account.subStatus = msg:getU8() + + account.premDays = msg:getU32() + if account.premDays ~= 0 and account.premDays ~= 65535 then + account.premDays = math.floor((account.premDays - os.time()) / 86400) + end + else + account.status = AccountStatus.Ok + account.premDays = msg:getU16() + account.subStatus = account.premDays > 0 and SubscriptionStatus.Premium or SubscriptionStatus.Free + end + + signalcall(self.onCharacterList, self, characters, account) +end + +function ProtocolLogin:parseExtendedCharacterList(msg) + local characters = msg:getTable() + local account = msg:getTable() + local otui = msg:getString() + signalcall(self.onCharacterList, self, characters, account, otui) +end + +function ProtocolLogin:parseOpcode(opcode, msg) + signalcall(self.onOpcode, self, opcode, msg) +end + +function ProtocolLogin:onError(msg, code) + local text = translateNetworkError(code, self:isConnecting(), msg) + signalcall(self.onLoginError, self, text) +end diff --git a/800OTClient/modules/gamelib/spells.lua b/800OTClient/modules/gamelib/spells.lua new file mode 100644 index 0000000..ffc6f21 --- /dev/null +++ b/800OTClient/modules/gamelib/spells.lua @@ -0,0 +1,472 @@ +SpelllistSettings = { + ['Default'] = { + iconFile = '/images/game/spells/defaultspells', + iconSize = {width = 32, height = 32}, + spellListWidth = 210, + spellWindowWidth = 550, + spellOrder = {'Animate Dead', 'Annihilation', 'Avalanche', 'Berserk', 'Blood Rage', 'Brutal Strike', 'Cancel Invisibility', 'Challenge', 'Chameleon', 'Charge', 'Conjure Arrow', 'Conjure Bolt', 'Conjure Explosive Arrow', 'Conjure Piercing Bolt', 'Conjure Poisoned Arrow', 'Conjure Power Bolt', 'Conjure Sniper Arrow', 'Convince Creature', 'Creature Illusion', 'Cure Bleeding', 'Cure Burning', 'Cure Curse', 'Cure Electrification', 'Cure Poison', 'Cure Poison Rune', 'Curse', 'Death Strike', 'Desintegrate', 'Destroy Field', 'Divine Caldera', 'Divine Healing', 'Divine Missile', 'Electrify', 'Enchant Party', 'Enchant Spear', 'Enchant Staff', 'Energy Beam', 'Energy Field', 'Energy Strike', 'Energy Wall', 'Energy Wave', 'Energybomb', 'Envenom', 'Eternal Winter', 'Ethereal Spear', 'Explosion', 'Fierce Berserk', 'Find Person', 'Fire Field', 'Fire Wall', 'Fire Wave', 'Fireball', 'Firebomb', 'Flame Strike', 'Food', 'Front Sweep', 'Great Energy Beam', 'Great Fireball', 'Great Light', 'Groundshaker', 'Haste', 'Heal Friend', 'Heal Party', 'Heavy Magic Missile', 'Hells Core', 'Holy Flash', 'Holy Missile', 'Ice Strike', 'Ice Wave', 'Icicle', 'Ignite', 'Inflict Wound', 'Intense Healing', 'Intense Healing Rune', 'Intense Recovery', 'Intense Wound Cleansing', 'Invisibility', 'Levitate', 'Light', 'Light Healing', 'Light Magic Missile', 'Lightning', 'Magic Rope', 'Magic Shield', 'Magic Wall', 'Mass Healing', 'Paralyze', 'Physical Strike', 'Poison Bomb', 'Poison Field', 'Poison Wall', 'Protect Party', 'Protector', 'Rage of the Skies', 'Recovery', 'Salvation', 'Sharpshooter', 'Soulfire', 'Stalagmite', 'Stone Shower', 'Strong Energy Strike', 'Strong Ethereal Spear', 'Strong Flame Strike', 'Strong Haste', 'Strong Ice Strike', 'Strong Ice Wave', 'Strong Terra Strike', 'Sudden Death', 'Summon Creature', 'Swift Foot', 'Terra Strike', 'Terra Wave', 'Thunderstorm', 'Train Party', 'Ultimate Energy Strike', 'Ultimate Flame Strike', 'Ultimate Healing', 'Ultimate Healing Rune', 'Ultimate Ice Strike', 'Ultimate Light', 'Ultimate Terra Strike', 'Whirlwind Throw', 'Wild Growth', 'Wound Cleansing', 'Wrath of Nature'} + }--[[, + + ['Sample'] = { + iconFile = '/images/game/spells/sample', + iconSize = {width = 64, height = 64}, + spellOrder = {'Critical Strike', 'Firefly', 'Fire Breath', 'Moonglaives', 'Wind Walk'} + }]] +} + +SpellInfo = { + ['Default'] = { + ['Death Strike'] = {id = 87, words = 'exori mort', exhaustion = 2000, premium = true, type = 'Instant', icon = 'deathstrike', mana = 20, level = 16, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, + ['Flame Strike'] = {id = 89, words = 'exori flam', exhaustion = 2000, premium = true, type = 'Instant', icon = 'flamestrike', mana = 20, level = 14, soul = 0, group = {[1] = 2000}, vocations = {1, 2, 5, 6}}, + ['Strong Flame Strike'] = {id = 150, words = 'exori gran flam', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongflamestrike', mana = 60, level = 70, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {1, 5}}, + ['Ultimate Flame Strike'] = {id = 154, words = 'exori max flam', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ultimateflamestrike', mana = 100, level = 90, soul = 0, group = {[1] = 4000}, vocations = {1, 5}}, + ['Energy Strike'] = {id = 88, words = 'exori vis', exhaustion = 2000, premium = true, type = 'Instant', icon = 'energystrike', mana = 20, level = 12, soul = 0, group = {[1] = 2000}, vocations = {1, 2, 5, 6}}, + ['Strong Energy Strike'] = {id = 151, words = 'exori gran vis', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongenergystrike', mana = 60, level = 80, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {1, 5}}, + ['Ultimate Energy Strike'] = {id = 155, words = 'exori max vis', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ultimateenergystrike', mana = 100, level = 100,soul = 0, group = {[1] = 4000}, vocations = {1, 5}}, + ['Whirlwind Throw'] = {id = 107, words = 'exori hur', exhaustion = 6000, premium = true, type = 'Instant', icon = 'whirlwindthrow', mana = 40, level = 28, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, + ['Fire Wave'] = {id = 19, words = 'exevo flam hur', exhaustion = 4000, premium = false, type = 'Instant', icon = 'firewave', mana = 25, level = 18, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, + ['Ethereal Spear'] = {id = 111, words = 'exori con', exhaustion = 2000, premium = true, type = 'Instant', icon = 'etherealspear', mana = 25, level = 23, soul = 0, group = {[1] = 2000}, vocations = {3, 7}}, + ['Strong Ethereal Spear'] = {id = 57, words = 'exori gran con', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongetherealspear', mana = 55, level = 90, soul = 0, group = {[1] = 2000}, vocations = {3, 7}}, + ['Energy Beam'] = {id = 22, words = 'exevo vis lux', exhaustion = 4000, premium = false, type = 'Instant', icon = 'energybeam', mana = 40, level = 23, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, + ['Great Energy Beam'] = {id = 23, words = 'exevo gran vis lux', exhaustion = 6000, premium = false, type = 'Instant', icon = 'greatenergybeam', mana = 110, level = 29, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, + ['Groundshaker'] = {id = 106, words = 'exori mas', exhaustion = 8000, premium = true, type = 'Instant', icon = 'groundshaker', mana = 160, level = 33, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, + ['Berserk'] = {id = 80, words = 'exori', exhaustion = 4000, premium = true, type = 'Instant', icon = 'berserk', mana = 115, level = 35, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, + ['Annihilation'] = {id = 62, words = 'exori gran ico', exhaustion = 30000, premium = true, type = 'Instant', icon = 'annihilation', mana = 300, level = 110,soul = 0, group = {[1] = 4000}, vocations = {4, 8}}, + ['Brutal Strike'] = {id = 61, words = 'exori ico', exhaustion = 6000, premium = true, type = 'Instant', icon = 'brutalstrike', mana = 30, level = 16, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, + ['Front Sweep'] = {id = 59, words = 'exori min', exhaustion = 6000, premium = true, type = 'Instant', icon = 'frontsweep', mana = 200, level = 70, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, + ['Inflict Wound'] = {id = 141, words = 'utori kor', exhaustion = 30000, premium = true, type = 'Instant', icon = 'inflictwound', mana = 30, level = 40, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, + ['Ignite'] = {id = 138, words = 'utori flam', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ignite', mana = 30, level = 26, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, + ['Lightning'] = {id = 149, words = 'exori amp vis', exhaustion = 8000, premium = true, type = 'Instant', icon = 'lightning', mana = 60, level = 55, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {1, 5}}, + ['Curse'] = {id = 139, words = 'utori mort', exhaustion = 50000, premium = true, type = 'Instant', icon = 'curse', mana = 30, level = 75, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, + ['Electrify'] = {id = 140, words = 'utori vis', exhaustion = 30000, premium = true, type = 'Instant', icon = 'electrify', mana = 30, level = 34, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, + ['Energy Wave'] = {id = 13, words = 'exevo vis hur', exhaustion = 8000, premium = false, type = 'Instant', icon = 'energywave', mana = 170, level = 38, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, + ['Rage of the Skies'] = {id = 119, words = 'exevo gran mas vis', exhaustion = 40000, premium = true, type = 'Instant', icon = 'rageoftheskies', mana = 600, level = 55, soul = 0, group = {[1] = 4000}, vocations = {1, 5}}, + ['Fierce Berserk'] = {id = 105, words = 'exori gran', exhaustion = 6000, premium = true, type = 'Instant', icon = 'fierceberserk', mana = 340, level = 90, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, + ['Hells Core'] = {id = 24, words = 'exevo gran mas flam', exhaustion = 40000, premium = true, type = 'Instant', icon = 'hellscore', mana = 1100, level = 60, soul = 0, group = {[1] = 4000}, vocations = {1, 5}}, + ['Holy Flash'] = {id = 143, words = 'utori san', exhaustion = 40000, premium = true, type = 'Instant', icon = 'holyflash', mana = 30, level = 70, soul = 0, group = {[1] = 2000}, vocations = {3, 7}}, + ['Divine Missile'] = {id = 122, words = 'exori san', exhaustion = 2000, premium = true, type = 'Instant', icon = 'divinemissile', mana = 20, level = 40, soul = 0, group = {[1] = 2000}, vocations = {3, 7}}, + ['Divine Caldera'] = {id = 124, words = 'exevo mas san', exhaustion = 4000, premium = true, type = 'Instant', icon = 'divinecaldera', mana = 160, level = 50, soul = 0, group = {[1] = 2000}, vocations = {3, 7}}, + ['Physical Strike'] = {id = 148, words = 'exori moe ico', exhaustion = 2000, premium = true, type = 'Instant', icon = 'physicalstrike', mana = 20, level = 16, soul = 0, group = {[1] = 2000}, vocations = {2, 6}}, + ['Eternal Winter'] = {id = 118, words = 'exevo gran mas frigo', exhaustion = 40000, premium = true, type = 'Instant', icon = 'eternalwinter', mana = 1050, level = 60, soul = 0, group = {[1] = 4000}, vocations = {2, 6}}, + ['Ice Strike'] = {id = 112, words = 'exori frigo', exhaustion = 2000, premium = true, type = 'Instant', icon = 'icestrike', mana = 20, level = 15, soul = 0, group = {[1] = 2000}, vocations = {1, 5, 2, 6}}, + ['Strong Ice Strike'] = {id = 152, words = 'exori gran frigo', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongicestrike', mana = 60, level = 80, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {2, 6}}, + ['Ultimate Ice Strike'] = {id = 156, words = 'exori max frigo', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ultimateicestrike', mana = 100, level = 100,soul = 0, group = {[1] = 4000}, vocations = {2, 6}}, + ['Ice Wave'] = {id = 121, words = 'exevo frigo hur', exhaustion = 4000, premium = false, type = 'Instant', icon = 'icewave', mana = 25, level = 18, soul = 0, group = {[1] = 2000}, vocations = {2, 6}}, + ['Strong Ice Wave'] = {id = 43, words = 'exevo gran frigo hur', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongicewave', mana = 170, level = 40, soul = 0, group = {[1] = 2000}, vocations = {2, 6}}, + ['Envenom'] = {id = 142, words = 'utori pox', exhaustion = 40000, premium = true, type = 'Instant', icon = 'envenom', mana = 30, level = 50, soul = 0, group = {[1] = 2000}, vocations = {2, 6}}, + ['Terra Strike'] = {id = 113, words = 'exori tera', exhaustion = 2000, premium = true, type = 'Instant', icon = 'terrastrike', mana = 20, level = 13, soul = 0, group = {[1] = 2000}, vocations = {1, 5, 2, 6}}, + ['Strong Terra Strike'] = {id = 153, words = 'exori gran tera', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongterrastrike', mana = 60, level = 70, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {2, 6}}, + ['Ultimate Terra Strike'] = {id = 157, words = 'exori max tera', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ultimateterrastrike', mana = 100, level = 90, soul = 0, group = {[1] = 4000}, vocations = {2, 6}}, + ['Terra Wave'] = {id = 120, words = 'exevo tera hur', exhaustion = 4000, premium = false, type = 'Instant', icon = 'terrawave', mana = 210, level = 38, soul = 0, group = {[1] = 2000}, vocations = {2, 6}}, + ['Wrath of Nature'] = {id = 56, words = 'exevo gran mas tera', exhaustion = 40000, premium = true, type = 'Instant', icon = 'wrathofnature', mana = 700, level = 55, soul = 0, group = {[1] = 4000}, vocations = {2, 6}}, + ['Light Healing'] = {id = 1, words = 'exura', exhaustion = 1000, premium = false, type = 'Instant', icon = 'lighthealing', mana = 20, level = 9, soul = 0, group = {[2] = 1000}, vocations = {1, 2, 3, 5, 6, 7}}, + ['Wound Cleansing'] = {id = 123, words = 'exura ico', exhaustion = 1000, premium = false, type = 'Instant', icon = 'woundcleansing', mana = 40, level = 10, soul = 0, group = {[2] = 1000}, vocations = {4, 8}}, + ['Intense Wound Cleansing'] = {id = 158, words = 'exura gran ico', exhaustion = 600000,premium = true, type = 'Instant', icon = 'intensewoundcleansing', mana = 200, level = 80, soul = 0, group = {[2] = 1000}, vocations = {4, 8}}, + ['Cure Bleeding'] = {id = 144, words = 'exana kor', exhaustion = 6000, premium = true, type = 'Instant', icon = 'curebleeding', mana = 30, level = 30, soul = 0, group = {[2] = 1000}, vocations = {4, 8}}, + ['Cure Electrification'] = {id = 146, words = 'exana vis', exhaustion = 6000, premium = true, type = 'Instant', icon = 'curseelectrification', mana = 30, level = 22, soul = 0, group = {[2] = 1000}, vocations = {2, 6}}, + ['Cure Poison'] = {id = 29, words = 'exana pox', exhaustion = 6000, premium = false, type = 'Instant', icon = 'curepoison', mana = 30, level = 10, soul = 0, group = {[2] = 1000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}}, + ['Cure Burning'] = {id = 145, words = 'exana flam', exhaustion = 6000, premium = true, type = 'Instant', icon = 'cureburning', mana = 30, level = 30, soul = 0, group = {[2] = 1000}, vocations = {2, 6}}, + ['Cure Curse'] = {id = 147, words = 'exana mort', exhaustion = 6000, premium = true, type = 'Instant', icon = 'curecurse', mana = 40, level = 80, soul = 0, group = {[2] = 1000}, vocations = {3, 7}}, + ['Recovery'] = {id = 159, words = 'utura', exhaustion = 60000, premium = true, type = 'Instant', icon = 'recovery', mana = 75, level = 50, soul = 0, group = {[2] = 1000}, vocations = {4, 8, 3, 7}}, + ['Intense Recovery'] = {id = 160, words = 'utura gran', exhaustion = 60000, premium = true, type = 'Instant', icon = 'intenserecovery', mana = 165, level = 100,soul = 0, group = {[2] = 1000}, vocations = {4, 8, 3, 7}}, + ['Salvation'] = {id = 36, words = 'exura gran san', exhaustion = 1000, premium = true, type = 'Instant', icon = 'salvation', mana = 210, level = 60, soul = 0, group = {[2] = 1000}, vocations = {3, 7}}, + ['Intense Healing'] = {id = 2, words = 'exura gran', exhaustion = 1000, premium = false, type = 'Instant', icon = 'intensehealing', mana = 70, level = 20, soul = 0, group = {[2] = 1000}, vocations = {1, 2, 3, 5, 6, 7}}, + ['Heal Friend'] = {id = 84, words = 'exura sio', exhaustion = 1000, premium = true, type = 'Instant', icon = 'healfriend', mana = 140, level = 18, soul = 0, group = {[2] = 1000}, vocations = {2, 6}}, + ['Ultimate Healing'] = {id = 3, words = 'exura vita', exhaustion = 1000, premium = false, type = 'Instant', icon = 'ultimatehealing', mana = 160, level = 30, soul = 0, group = {[2] = 1000}, vocations = {1, 2, 5, 6}}, + ['Mass Healing'] = {id = 82, words = 'exura gran mas res', exhaustion = 2000, premium = true, type = 'Instant', icon = 'masshealing', mana = 150, level = 36, soul = 0, group = {[2] = 1000}, vocations = {2, 6}}, + ['Divine Healing'] = {id = 125, words = 'exura san', exhaustion = 1000, premium = false, type = 'Instant', icon = 'divinehealing', mana = 160, level = 35, soul = 0, group = {[2] = 1000}, vocations = {3, 7}}, + ['Light'] = {id = 10, words = 'utevo lux', exhaustion = 2000, premium = false, type = 'Instant', icon = 'light', mana = 20, level = 8, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}}, + ['Find Person'] = {id = 20, words = 'exiva', exhaustion = 2000, premium = false, type = 'Instant', icon = 'findperson', mana = 20, level = 8, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}}, + ['Magic Rope'] = {id = 76, words = 'exani tera', exhaustion = 2000, premium = true, type = 'Instant', icon = 'magicrope', mana = 20, level = 9, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}}, + ['Levitate'] = {id = 81, words = 'exani hur', exhaustion = 2000, premium = true, type = 'Instant', icon = 'levitate', mana = 50, level = 12, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}}, + ['Great Light'] = {id = 11, words = 'utevo gran lux', exhaustion = 2000, premium = false, type = 'Instant', icon = 'greatlight', mana = 60, level = 13, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}}, + ['Magic Shield'] = {id = 44, words = 'utamo vita', exhaustion = 2000, premium = false, type = 'Instant', icon = 'magicshield', mana = 50, level = 14, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Haste'] = {id = 6, words = 'utani hur', exhaustion = 2000, premium = true, type = 'Instant', icon = 'haste', mana = 60, level = 14, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}}, + ['Charge'] = {id = 131, words = 'utani tempo hur', exhaustion = 2000, premium = true, type = 'Instant', icon = 'charge', mana = 100, level = 25, soul = 0, group = {[3] = 2000}, vocations = {4, 8}}, + ['Swift Foot'] = {id = 134, words = 'utamo tempo san', exhaustion = 2000, premium = true, type = 'Instant', icon = 'swiftfoot', mana = 400, level = 55, soul = 0, group = {[1] = 10000, [3] = 2000}, vocations = {3, 7}}, + ['Challenge'] = {id = 93, words = 'exeta res', exhaustion = 2000, premium = true, type = 'Instant', icon = 'challenge', mana = 30, level = 20, soul = 0, group = {[3] = 2000}, vocations = {8}}, + ['Strong Haste'] = {id = 39, words = 'utani gran hur', exhaustion = 2000, premium = true, type = 'Instant', icon = 'stronghaste', mana = 100, level = 20, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Creature Illusion'] = {id = 38, words = 'utevo res ina', exhaustion = 2000, premium = false, type = 'Instant', icon = 'creatureillusion', mana = 100, level = 23, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Ultimate Light'] = {id = 75, words = 'utevo vis lux', exhaustion = 2000, premium = true, type = 'Instant', icon = 'ultimatelight', mana = 140, level = 26, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Cancel Invisibility'] = {id = 90, words = 'exana ina', exhaustion = 2000, premium = true, type = 'Instant', icon = 'cancelinvisibility', mana = 200, level = 26, soul = 0, group = {[3] = 2000}, vocations = {3, 7}}, + ['Invisibility'] = {id = 45, words = 'utana vid', exhaustion = 2000, premium = false, type = 'Instant', icon = 'invisible', mana = 440, level = 35, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Sharpshooter'] = {id = 135, words = 'utito tempo san', exhaustion = 2000, premium = true, type = 'Instant', icon = 'sharpshooter', mana = 450, level = 60, soul = 0, group = {[2] = 10000, [3] = 10000}, vocations = {3, 7}}, + ['Protector'] = {id = 132, words = 'utamo tempo', exhaustion = 2000, premium = true, type = 'Instant', icon = 'protector', mana = 200, level = 55, soul = 0, group = {[1] = 10000, [3] = 2000}, vocations = {4, 8}}, + ['Blood Rage'] = {id = 133, words = 'utito tempo', exhaustion = 2000, premium = true, type = 'Instant', icon = 'bloodrage', mana = 290, level = 60, soul = 0, group = {[3] = 2000}, vocations = {4, 8}}, + ['Train Party'] = {id = 126, words = 'utito mas sio', exhaustion = 2000, premium = true, type = 'Instant', icon = 'trainparty', mana = 'Var.', level = 32, soul = 0, group = {[3] = 2000}, vocations = {8}}, + ['Protect Party'] = {id = 127, words = 'utamo mas sio', exhaustion = 2000, premium = true, type = 'Instant', icon = 'protectparty', mana = 'Var.', level = 32, soul = 0, group = {[3] = 2000}, vocations = {7}}, + ['Heal Party'] = {id = 128, words = 'utura mas sio', exhaustion = 2000, premium = true, type = 'Instant', icon = 'healparty', mana = 'Var.', level = 32, soul = 0, group = {[3] = 2000}, vocations = {6}}, + ['Enchant Party'] = {id = 129, words = 'utori mas sio', exhaustion = 2000, premium = true, type = 'Instant', icon = 'enchantparty', mana = 'Var.', level = 32, soul = 0, group = {[3] = 2000}, vocations = {5}}, + ['Summon Creature'] = {id = 9, words = 'utevo res', exhaustion = 2000, premium = false, type = 'Instant', icon = 'summoncreature', mana = 'Var.', level = 25, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Conjure Arrow'] = {id = 51, words = 'exevo con', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'conjurearrow', mana = 100, level = 13, soul = 1, group = {[3] = 2000}, vocations = {3, 7}}, + ['Food'] = {id = 42, words = 'exevo pan', exhaustion = 2000, premium = false, type = 'Instant', icon = 'food', mana = 120, level = 14, soul = 1, group = {[3] = 2000}, vocations = {2, 6}}, + ['Conjure Poisoned Arrow'] = {id = 48, words = 'exevo con pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'poisonedarrow', mana = 130, level = 16, soul = 2, group = {[3] = 2000}, vocations = {3, 7}}, + ['Conjure Bolt'] = {id = 79, words = 'exevo con mort', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'conjurebolt', mana = 140, level = 17, soul = 2, group = {[3] = 2000}, vocations = {3, 7}}, + ['Conjure Sniper Arrow'] = {id = 108, words = 'exevo con hur', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'sniperarrow', mana = 160, level = 24, soul = 3, group = {[3] = 2000}, vocations = {3, 7}}, + ['Conjure Explosive Arrow'] = {id = 49, words = 'exevo con flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'explosivearrow', mana = 290, level = 25, soul = 3, group = {[3] = 2000}, vocations = {3, 7}}, + ['Conjure Piercing Bolt'] = {id = 109, words = 'exevo con grav', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'piercingbolt', mana = 180, level = 33, soul = 3, group = {[3] = 2000}, vocations = {3, 7}}, + ['Enchant Staff'] = {id = 92, words = 'exeta vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'enchantstaff', mana = 80, level = 41, soul = 0, group = {[3] = 2000}, vocations = {5}}, + ['Enchant Spear'] = {id = 110, words = 'exeta con', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'enchantspear', mana = 350, level = 45, soul = 3, group = {[3] = 2000}, vocations = {3, 7}}, + ['Conjure Power Bolt'] = {id = 95, words = 'exevo con vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'powerbolt', mana = 800, level = 59, soul = 3, group = {[3] = 2000}, vocations = {7}}, + ['Poison Field'] = {id = 26, words = 'adevo grav pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'poisonfield', mana = 200, level = 14, soul = 1, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Light Magic Missile'] = {id = 7, words = 'adori min vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'lightmagicmissile', mana = 120, level = 15, soul = 1, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Fire Field'] = {id = 25, words = 'adevo grav flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'firefield', mana = 240, level = 15, soul = 1, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Fireball'] = {id = 15, words = 'adori flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'fireball', mana = 460, level = 27, soul = 3, group = {[3] = 2000}, vocations = {1, 5}}, + ['Energy Field'] = {id = 27, words = 'adevo grav vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'energyfield', mana = 320, level = 18, soul = 2, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Stalagmite'] = {id = 77, words = 'adori tera', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'stalagmite', mana = 400, level = 24, soul = 2, group = {[3] = 2000}, vocations = {1, 5, 2, 6}}, + ['Great Fireball'] = {id = 16, words = 'adori mas flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'greatfireball', mana = 530, level = 30, soul = 3, group = {[3] = 2000}, vocations = {1, 5}}, + ['Heavy Magic Missile'] = {id = 8, words = 'adori vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'heavymagicmissile', mana = 350, level = 25, soul = 2, group = {[3] = 2000}, vocations = {1, 5, 2, 6}}, + ['Poison Bomb'] = {id = 91, words = 'adevo mas pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'poisonbomb', mana = 520, level = 25, soul = 2, group = {[3] = 2000}, vocations = {2, 6}}, + ['Firebomb'] = {id = 17, words = 'adevo mas flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'firebomb', mana = 600, level = 27, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Soulfire'] = {id = 50, words = 'adevo res flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'soulfire', mana = 600, level = 27, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Poison Wall'] = {id = 32, words = 'adevo mas grav pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'poisonwall', mana = 640, level = 29, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Explosion'] = {id = 18, words = 'adevo mas hur', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'explosion', mana = 570, level = 31, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Fire Wall'] = {id = 28, words = 'adevo mas grav flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'firewall', mana = 780, level = 33, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Energybomb'] = {id = 55, words = 'adevo mas vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'energybomb', mana = 880, level = 37, soul = 5, group = {[3] = 2000}, vocations = {1, 5}}, + ['Energy Wall'] = {id = 33, words = 'adevo mas grav vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'energywall', mana = 1000, level = 41, soul = 5, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Sudden Death'] = {id = 21, words = 'adori gran mort', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'suddendeath', mana = 985, level = 45, soul = 5, group = {[3] = 2000}, vocations = {1, 5}}, + ['Cure Poison Rune'] = {id = 31, words = 'adana pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'antidote', mana = 200, level = 15, soul = 1, group = {[3] = 2000}, vocations = {2, 6}}, + ['Intense Healing Rune'] = {id = 4, words = 'adura gran', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'intensehealingrune', mana = 240, level = 15, soul = 2, group = {[3] = 2000}, vocations = {2, 6}}, + ['Ultimate Healing Rune'] = {id = 5, words = 'adura vita', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'ultimatehealingrune', mana = 400, level = 24, soul = 3, group = {[3] = 2000}, vocations = {2, 6}}, + ['Convince Creature'] = {id = 12, words = 'adeta sio', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'convincecreature', mana = 200, level = 16, soul = 3, group = {[3] = 2000}, vocations = {2, 6}}, + ['Animate Dead'] = {id = 83, words = 'adana mort', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'animatedead', mana = 600, level = 27, soul = 5, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Chameleon'] = {id = 14, words = 'adevo ina', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'chameleon', mana = 600, level = 27, soul = 2, group = {[3] = 2000}, vocations = {2, 6}}, + ['Destroy Field'] = {id = 30, words = 'adito grav', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'destroyfield', mana = 120, level = 17, soul = 2, group = {[3] = 2000}, vocations = {1, 2, 3, 5, 6, 7}}, + ['Desintegrate'] = {id = 78, words = 'adito tera', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'desintegrate', mana = 200, level = 21, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 3, 5, 6, 7}}, + ['Magic Wall'] = {id = 86, words = 'adevo grav tera', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'magicwall', mana = 750, level = 32, soul = 5, group = {[3] = 2000}, vocations = {1, 5}}, + ['Wild Growth'] = {id = 94, words = 'adevo grav vita', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'wildgrowth', mana = 600, level = 27, soul = 5, group = {[3] = 2000}, vocations = {2, 6}}, + ['Paralyze'] = {id = 54, words = 'adana ani', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'paralyze', mana = 1400, level = 54, soul = 3, group = {[3] = 2000}, vocations = {2, 6}}, + ['Icicle'] = {id = 114, words = 'adori frigo', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'icicle', mana = 460, level = 28, soul = 3, group = {[3] = 2000}, vocations = {2, 6}}, + ['Avalanche'] = {id = 115, words = 'adori mas frigo', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'avalanche', mana = 530, level = 30, soul = 3, group = {[3] = 2000}, vocations = {2, 6}}, + ['Stone Shower'] = {id = 116, words = 'adori mas tera', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'stoneshower', mana = 430, level = 28, soul = 3, group = {[3] = 2000}, vocations = {2, 6}}, + ['Thunderstorm'] = {id = 117, words = 'adori mas vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'thunderstorm', mana = 430, level = 28, soul = 3, group = {[3] = 2000}, vocations = {1, 5}}, + ['Holy Missile'] = {id = 130, words = 'adori san', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'holymissile', mana = 350, level = 27, soul = 3, group = {[3] = 2000}, vocations = {3, 7}}, + -- newest tibia spells + ['Summon Paladin Familiar'] = {id = 171, words = 'utevo gran res sac', exhaustion = 1800000,premium = true, type = 'Instant', icon = 'summonpaladinfamiliar', mana = 2000, level = 200,soul = 0, group = {[3] = 2000}, vocations = {3, 7}}, + ['Summon Knight Familiar'] = {id = 170, words = 'utevo gran res eq', exhaustion = 1800000,premium = true, type = 'Instant', icon = 'summonknightfamiliar', mana = 1000, level = 200,soul = 0, group = {[3] = 2000}, vocations = {3, 7}}, + ['Summon Druid Familiar'] = {id = 172, words = 'utevo gran res dru', exhaustion = 1800000,premium = true, type = 'Instant', icon = 'summondruidfamiliar', mana = 3000, level = 200,soul = 0, group = {[3] = 2000}, vocations = {3, 7}}, + ['Summon Sorcerer Familiar'] = {id = 173, words = 'utevo gran res eq', exhaustion = 1800000,premium = true, type = 'Instant', icon = 'summonsorcererfamiliar', mana = 3000, level = 200,soul = 0, group = {[3] = 2000}, vocations = {3, 7}}, + ['Chivalrous Challenge'] = {id = 101, words = "exeta amp res", exhaustion = 2000, premium = true, type = 'Instant', icon = 'chivalrouschallange', mana = 80, level = 150,soul = 0, group = {[3] = 2000}, vocations = {8}}, + ['Fair Wound Cleansing'] = {id = 102, words = 'exura med ico', exhaustion = 1000, premium = true, type = 'Instant', icon = 'fairwoundcleansing', mana = 90, level = 300,soul = 0, group = {[2] = 1000}, vocations = {8}}, + ['Conjure Wand of Darkness'] = {id = 92, words = 'exevo gran mort', exhaustion = 1800000,premium = true, type = 'Conjure', icon = 'conjurewandofdarkness', mana = 250, level = 41, soul = 0, group = {[3] = 2000}, vocations = {1, 5}}, + ['Expose Weakness'] = {id = 106, words = 'exori moe', exhaustion = 12000, premium = true, type = 'Instant', icon = 'exposeweakness', mana = 400, level = 275,soul = 0, group = {[5] = 12000, [3] = 2000}, vocations = {1, 5}}, + ['Sap Strenght'] = {id = 105, words = 'exori kor', exhaustion = 12000, premium = true, type = 'Instant', icon = 'sapstrenght', mana = 300, level = 175,soul = 0, group = {[5] = 12000, [3] = 2000}, vocations = {1, 5}}, + ['Great Fire Wave'] = {id = 100, words = 'exevo gran flam hur', exhaustion = 4000, premium = true, type = 'Instant', icon = 'greatfirewave', mana = 120, level = 38, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, + ['Restoration'] = {id = 103, words = "exura max vita", exhaustion = 6000, premium = true, type = 'Instant', icon = 'restoration', mana = 260, level = 300,soul = 0, group = {[2] = 1000}, vocations = {1, 2, 5, 6}}, + ["Nature's Embrace"] = {id = 101, words = 'exura gran sio', exhaustion = 60000, premium = true, type = 'Instant', icon = 'naturesembrace', mana = 400, level = 300,soul = 0, group = {[2] = 1000}, vocations = {2, 6}}, + ['Divine Dazzle'] = {id = 101, words = 'exana amp res', exhaustion = 16000, premium = true, type = 'Instant', icon = 'divinedazzle', mana = 80, level = 250,soul = 0, group = {[3] = 2000}, vocations = {3, 7}}, + + }--[[, + + ['Sample'] = { + ['Wind Walk'] = {id = 1, words = 'windwalk', description = 'Run at enormous speed.', exhaustion = 2000, premium = false, type = 'Instant', icon = 1, mana = 50, level = 10, soul = 0, group = {[3] = 2000}, vocations = {1, 2}}, + ['Fire Breath'] = {id = 2, words = 'firebreath', description = 'A strong firewave.', exhaustion = 2000, premium = false, type = 'Instant', icon = 2, mana = 350, level = 27, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, + ['Moonglaives'] = {id = 3, words = 'moonglaives', description = 'Throw moonglaives around you.', exhaustion = 2000, premium = false, type = 'Instant', icon = 3, mana = 90, level = 55, soul = 0, group = {[1] = 2000}, vocations = {3, 7}}, + ['Critical Strike'] = {id = 4, words = 'criticalstrike', description = 'Land a critical strike.', exhaustion = 2000, premium = false, type = 'Instant', icon = 4, mana = 350, level = 27, soul = 0, group = {[1] = 2000}, vocations = {3, 4, 7, 8}}, + ['Firefly'] = {id = 5, words = 'firefly', description = 'Summon a angry firefly', exhaustion = 2000, premium = false, type = 'Instant', icon = 5, mana = 350, level = 27, soul = 0, group = {[1] = 2000}, vocations = {1, 2, 5, 6}} + }]] +} + +-- ['const_name'] = {client_id, TFS_id} +-- Conversion from TFS icon id to the id used by client (icons.png order) +SpellIcons = { + -- new tibia spells, server owners - you will probably need to adjust TFS_id + ['summonsorcererfamiliar'] = {130, 173}, + ['summondruidfamiliar'] = {129, 172}, + ['summonpaladinfamiliar'] = {127, 171}, + ['summonknightfamiliar'] = {128, 170}, + ['exposeweakness'] = {134, 106}, + ['sapstrenght'] = {135, 105}, + ['restoration'] = {137, 103}, + ['fairwoundcleansing'] = {132, 102}, + ['chivalrouschallange'] = {131, 101}, + ["naturesembrace"] = {138, 101}, + ['divinedazzle'] = {139, 101}, + ['greatfirewave'] = {136, 100}, + ['conjurewandofdarkness'] = {133, 92}, + -- old spells + ['intenserecovery'] = {16, 160}, + ['recovery'] = {15, 159}, + ['intensewoundcleansing'] = {4, 158}, + ['ultimateterrastrike'] = {37, 157}, + ['ultimateicestrike'] = {34, 156}, + ['ultimateenergystrike'] = {31, 155}, + ['ultimateflamestrike'] = {28, 154}, + ['strongterrastrike'] = {36, 153}, + ['strongicestrike'] = {33, 152}, + ['strongenergystrike'] = {30, 151}, + ['strongflamestrike'] = {27, 150}, + ['lightning'] = {51, 149}, + ['physicalstrike'] = {17, 148}, + ['curecurse'] = {11, 147}, + ['curseelectrification'] = {14, 146}, + ['cureburning'] = {13, 145}, + ['curebleeding'] = {12, 144}, + ['holyflash'] = {53, 143}, + ['envenom'] = {58, 142}, + ['inflictwound'] = {57, 141}, + ['electrify'] = {56, 140}, + ['curse'] = {54, 139}, + ['ignite'] = {55, 138}, + -- [[ 136 / 137 Unknown ]] + ['sharpshooter'] = {121, 135}, + ['swiftfoot'] = {119, 134}, + ['bloodrage'] = {96, 133}, + ['protector'] = {122, 132}, + ['charge'] = {98, 131}, + ['holymissile'] = {76, 130}, + ['enchantparty'] = {113, 129}, + ['healparty'] = {126, 128}, + ['protectparty'] = {123, 127}, + ['trainparty'] = {120, 126}, + ['divinehealing'] = {2, 125}, + ['divinecaldera'] = {40, 124}, + ['woundcleansing'] = {3, 123}, + ['divinemissile'] = {39, 122}, + ['icewave'] = {45, 121}, + ['terrawave'] = {47, 120}, + ['rageoftheskies'] = {52, 119}, + ['eternalwinter'] = {50, 118}, + ['thunderstorm'] = {63, 117}, + ['stoneshower'] = {65, 116}, + ['avalanche'] = {92, 115}, + ['icicle'] = {75, 114}, + ['terrastrike'] = {35, 113}, + ['icestrike'] = {32, 112}, + ['etherealspear'] = {18, 111}, + ['enchantspear'] = {104, 110}, + ['piercingbolt'] = {110, 109}, + ['sniperarrow'] = {112, 108}, + ['whirlwindthrow'] = {19, 107}, + ['groundshaker'] = {25, 106}, + ['fierceberserk'] = {22, 105}, + -- [[ 96 - 104 Unknown ]] + ['powerbolt'] = {108, 95}, + ['wildgrowth'] = {61, 94}, + ['challenge'] = {97, 93}, + ['enchantstaff'] = {103, 92}, + ['poisonbomb'] = {70, 91}, + ['cancelinvisibility'] = {95, 90}, + ['flamestrike'] = {26, 89}, + ['energystrike'] = {29, 88}, + ['deathstrike'] = {38, 87}, + ['magicwall'] = {72, 86}, + ['healfriend'] = {8, 84}, + ['animatedead'] = {93, 83}, + ['masshealing'] = {9, 82}, + ['levitate'] = {125, 81}, + ['berserk'] = {21, 80}, + ['conjurebolt'] = {107, 79}, + ['desintegrate'] = {88, 78}, + ['stalagmite'] = {66, 77}, + ['magicrope'] = {105, 76}, + ['ultimatelight'] = {115, 75}, + -- [[ 71 - 64 TFS House Commands ]] + -- [[ 63 - 70 Unknown ]] + ['annihilation'] = {24, 62}, + ['brutalstrike'] = {23, 61}, + -- [[ 60 Unknown ]] + ['frontsweep'] = {20, 59}, + -- [[ 58 Unknown ]] + ['strongetherealspear'] = {59, 57}, + ['wrathofnature'] = {48, 56}, + ['energybomb'] = {86, 55}, + ['paralyze'] = {71, 54}, + -- [[ 53 Unknown ]] + -- [[ 52 TFS Retrieve Friend ]] + ['conjurearrow'] = {106, 51}, + ['soulfire'] = {67, 50}, + ['explosivearrow'] = {109, 49}, + ['poisonedarrow'] = {111, 48}, + -- [[ 46 / 47 Unknown ]] + ['invisible'] = {94, 45}, + ['magicshield'] = {124, 44}, + ['strongicewave'] = {46, 43}, + ['food'] = {99, 42}, + -- [[ 40 / 41 Unknown ]] + ['stronghaste'] = {102, 39}, + ['creatureillusion'] = {100, 38}, + -- [[ 37 TFS Move ]] + ['salvation'] = {60, 36}, + -- [[ 34 / 35 Unknown ]] + ['energywall'] = {84, 33}, + ['poisonwall'] = {68, 32}, + ['antidote'] = {10, 31}, + ['destroyfield'] = {87, 30}, + ['curepoison'] = {10, 29}, + ['firewall'] = {80, 28}, + ['energyfield'] = {85, 27}, + ['poisonfield'] = {69, 26}, + ['firefield'] = {81, 25}, + ['hellscore'] = {49, 24}, + ['greatenergybeam'] = {42, 23}, + ['energybeam'] = {41, 22}, + ['suddendeath'] = {64, 21}, + ['findperson'] = {114, 20}, + ['firewave'] = {44, 19}, + ['explosion'] = {83, 18}, + ['firebomb'] = {82, 17}, + ['greatfireball'] = {78, 16}, + ['fireball'] = {79, 15}, + ['chameleon'] = {91, 14}, + ['energywave'] = {43, 13}, + ['convincecreature'] = {90, 12}, + ['greatlight'] = {116, 11}, + ['light'] = {117, 10}, + ['summoncreature'] = {118, 9}, + ['heavymagicmissile'] = {77, 8}, + ['lightmagicmissile'] = {73, 7}, + ['haste'] = {101, 6}, + ['ultimatehealingrune'] = {62, 5}, + ['intensehealingrune'] = {74, 4}, + ['ultimatehealing'] = {1, 3}, + ['intensehealing'] = {7, 2}, + ['lighthealing'] = {6, 1}, +} + +VocationNames = { + [1] = 'Sorcerer', + [2] = 'Druid', + [3] = 'Paladin', + [4] = 'Knight', + [5] = 'Master Sorcerer', + [6] = 'Elder Druid', + [7] = 'Royal Paladin', + [8] = 'Elite Knight' +} + +SpellGroups = { + [1] = 'Attack', + [2] = 'Healing', + [3] = 'Support', + [4] = 'Special', + [5] = 'Cripple' +} + +Spells = {} + +function Spells.getClientId(spellName) + local profile = Spells.getSpellProfileByName(spellName) + + local id = SpellInfo[profile][spellName].icon + if not tonumber(id) and SpellIcons[id] then + return SpellIcons[id][1] + end + return tonumber(id) +end + +function Spells.getServerId(spellName) + local profile = Spells.getSpellProfileByName(spellName) + + local id = SpellInfo[profile][spellName].icon + if not tonumber(id) and SpellIcons[id] then + return SpellIcons[id][2] + end + return tonumber(id) +end + +function Spells.getSpellByName(name) + return SpellInfo[Spells.getSpellProfileByName(name)][name] +end + +function Spells.getSpellByWords(words) + local words = words:lower():trim() + for profile,data in pairs(SpellInfo) do + for k,spell in pairs(data) do + if spell.words == words then + return spell, profile, k + end + end + end + return nil +end + +function Spells.getSpellByIcon(iconId) + for profile,data in pairs(SpellInfo) do + for k,spell in pairs(data) do + if spell.id == iconId then + return spell, profile, k + end + end + end + return nil +end + +function Spells.getSpellIconIds() + local ids = {} + for profile,data in pairs(SpellInfo) do + for k,spell in pairs(data) do + table.insert(ids, spell.id) + end + end + return ids +end + +function Spells.getSpellProfileById(id) + for profile,data in pairs(SpellInfo) do + for k,spell in pairs(data) do + if spell.id == id then + return profile + end + end + end + return nil +end + +function Spells.getSpellProfileByWords(words) + for profile,data in pairs(SpellInfo) do + for k,spell in pairs(data) do + if spell.words == words then + return profile + end + end + end + return nil +end + +function Spells.getSpellProfileByName(spellName) + for profile,data in pairs(SpellInfo) do + if table.findbykey(data, spellName:trim(), true) then + return profile + end + end + return nil +end + +function Spells.getSpellsByVocationId(vocId) + local spells = {} + for profile,data in pairs(SpellInfo) do + for k,spell in pairs(data) do + if table.contains(spell.vocations, vocId) then + table.insert(spells, spell) + end + end + end + return spells +end + +function Spells.filterSpellsByGroups(spells, groups) + local filtered = {} + for v,spell in pairs(spells) do + local spellGroups = Spells.getGroupIds(spell) + if table.equals(spellGroups, groups) then + table.insert(filtered, spell) + end + end + return filtered +end + +function Spells.getGroupIds(spell) + local groups = {} + for k,_ in pairs(spell.group) do + table.insert(groups, k) + end + return groups +end + +function Spells.getImageClip(id, profile) + return (((id-1)%12)*SpelllistSettings[profile].iconSize.width) .. ' ' + .. ((math.ceil(id/12)-1)*SpelllistSettings[profile].iconSize.height) .. ' ' + .. SpelllistSettings[profile].iconSize.width .. ' ' + .. SpelllistSettings[profile].iconSize.height +end diff --git a/800OTClient/modules/gamelib/textmessages.lua b/800OTClient/modules/gamelib/textmessages.lua new file mode 100644 index 0000000..43c0a6f --- /dev/null +++ b/800OTClient/modules/gamelib/textmessages.lua @@ -0,0 +1,30 @@ +local messageModeCallbacks = {} + +function g_game.onTextMessage(messageMode, message) + local callbacks = messageModeCallbacks[messageMode] + if not callbacks or #callbacks == 0 then + perror(string.format('Unhandled onTextMessage message mode %i: %s', messageMode, message)) + return + end + + for _, callback in pairs(callbacks) do + callback(messageMode, message) + end +end + +function registerMessageMode(messageMode, callback) + if not messageModeCallbacks[messageMode] then + messageModeCallbacks[messageMode] = {} + end + + table.insert(messageModeCallbacks[messageMode], callback) + return true +end + +function unregisterMessageMode(messageMode, callback) + if not messageModeCallbacks[messageMode] then + return false + end + + return table.removevalue(messageModeCallbacks[messageMode], callback) +end diff --git a/800OTClient/modules/gamelib/thing.lua b/800OTClient/modules/gamelib/thing.lua new file mode 100644 index 0000000..5dfa477 --- /dev/null +++ b/800OTClient/modules/gamelib/thing.lua @@ -0,0 +1,49 @@ +ThingCategoryItem = 0 +ThingCategoryCreature = 1 +ThingCategoryEffect = 2 +ThingCategoryMissile = 3 +ThingInvalidCategory = 4 +ThingLastCategory = ThingInvalidCategory + +ThingAttrGround = 0 +ThingAttrGroundBorder = 1 +ThingAttrOnBottom = 2 +ThingAttrOnTop = 3 +ThingAttrContainer = 4 +ThingAttrStackable = 5 +ThingAttrForceUse = 6 +ThingAttrMultiUse = 7 +ThingAttrWritable = 8 +ThingAttrWritableOnce = 9 +ThingAttrFluidContainer = 10 +ThingAttrSplash = 11 +ThingAttrNotWalkable = 12 +ThingAttrNotMoveable = 13 +ThingAttrBlockProjectile = 14 +ThingAttrNotPathable = 15 +ThingAttrPickupable = 16 +ThingAttrHangable = 17 +ThingAttrHookSouth = 18 +ThingAttrHookEast = 19 +ThingAttrRotateable = 20 +ThingAttrLight = 21 +ThingAttrDontHide = 22 +ThingAttrTranslucent = 23 +ThingAttrDisplacement = 24 +ThingAttrElevation = 25 +ThingAttrLyingCorpse = 26 +ThingAttrAnimateAlways = 27 +ThingAttrMinimapColor = 28 +ThingAttrLensHelp = 29 +ThingAttrFullGround = 30 +ThingAttrLook = 31 +ThingAttrCloth = 32 +ThingAttrMarket = 33 +ThingAttrNoMoveAnimation = 253 -- >= 1010, value = 16 +ThingAttrChargeable = 254 -- deprecated +ThingLastAttr = 255 + +SpriteMaskRed = 1 +SpriteMaskGreen = 2 +SpriteMaskBlue = 3 +SpriteMaskYellow = 4 \ No newline at end of file diff --git a/800OTClient/modules/gamelib/ui/uicreaturebutton.lua b/800OTClient/modules/gamelib/ui/uicreaturebutton.lua new file mode 100644 index 0000000..2517833 --- /dev/null +++ b/800OTClient/modules/gamelib/ui/uicreaturebutton.lua @@ -0,0 +1,156 @@ +-- @docclass +UICreatureButton = extends(UIWidget, "UICreatureButton") + +local CreatureButtonColors = { + onIdle = {notHovered = '#888888', hovered = '#FFFFFF' }, + onTargeted = {notHovered = '#FF0000', hovered = '#FF8888' }, + onFollowed = {notHovered = '#00FF00', hovered = '#88FF88' } +} + +local LifeBarColors = {} -- Must be sorted by percentAbove +table.insert(LifeBarColors, {percentAbove = 92, color = '#00BC00' } ) +table.insert(LifeBarColors, {percentAbove = 60, color = '#50A150' } ) +table.insert(LifeBarColors, {percentAbove = 30, color = '#A1A100' } ) +table.insert(LifeBarColors, {percentAbove = 8, color = '#BF0A0A' } ) +table.insert(LifeBarColors, {percentAbove = 3, color = '#910F0F' } ) +table.insert(LifeBarColors, {percentAbove = -1, color = '#850C0C' } ) + +function UICreatureButton.create() + local button = UICreatureButton.internalCreate() + button:setFocusable(false) + button.creature = nil + button.isHovered = false + return button +end + +function UICreatureButton:setCreature(creature) + self.creature = creature +end + +function UICreatureButton:getCreature() + return self.creature +end + +function UICreatureButton:getCreatureId() + return self.creature:getId() +end + +function UICreatureButton:setup(id) + self.lifeBarWidget = self:getChildById('lifeBar') + self.creatureWidget = self:getChildById('creature') + self.labelWidget = self:getChildById('label') + self.skullWidget = self:getChildById('skull') + self.emblemWidget = self:getChildById('emblem') +end + +function UICreatureButton:update() + local color = CreatureButtonColors.onIdle + local show = false + if self.creature == g_game.getAttackingCreature() then + color = CreatureButtonColors.onTargeted + elseif self.creature == g_game.getFollowingCreature() then + color = CreatureButtonColors.onFollowed + end + color = self.isHovered and color.hovered or color.notHovered + + if self.color == color then + return + end + self.color = color + + if color ~= CreatureButtonColors.onIdle.notHovered then + self.creatureWidget:setBorderWidth(1) + self.creatureWidget:setBorderColor(color) + self.labelWidget:setColor(color) + else + self.creatureWidget:setBorderWidth(0) + self.labelWidget:setColor(color) + end +end + +function UICreatureButton:creatureSetup(creature) + if self.creature ~= creature then + self.creature = creature + self.creatureWidget:setCreature(creature) + if self.creatureName ~= creature:getName() then + self.creatureName = creature:getName() + self.labelWidget:setText(creature:getName()) + end + end + + self:updateLifeBarPercent() + self:updateSkull() + self:updateEmblem() + self:update() +end + +function UICreatureButton:updateSkull() + if not self.creature then + return + end + local skullId = self.creature:getSkull() + if skullId == self.skullId then + return + end + self.skullId = skullId + + if skullId ~= SkullNone then + self.skullWidget:setWidth(self.skullWidget:getHeight()) + local imagePath = getSkullImagePath(skullId) + self.skullWidget:setImageSource(imagePath) + self.labelWidget:setMarginLeft(5) + else + self.skullWidget:setWidth(0) + if self.creature:getEmblem() == EmblemNone then + self.labelWidget:setMarginLeft(2) + end + end +end + +function UICreatureButton:updateEmblem() + if not self.creature then + return + end + local emblemId = self.creature:getEmblem() + if self.emblemId == emblemId then + return + end + self.emblemId = emblemId + + if emblemId ~= EmblemNone then + self.emblemWidget:setWidth(self.emblemWidget:getHeight()) + local imagePath = getEmblemImagePath(emblemId) + self.emblemWidget:setImageSource(imagePath) + self.emblemWidget:setMarginLeft(5) + self.labelWidget:setMarginLeft(5) + else + self.emblemWidget:setWidth(0) + self.emblemWidget:setMarginLeft(0) + if self.creature:getSkull() == SkullNone then + self.labelWidget:setMarginLeft(2) + end + end +end + +function UICreatureButton:updateLifeBarPercent() + if not self.creature then + return + end + local percent = self.creature:getHealthPercent() + if self.percent == percent then + return + end + + self.percent = percent + self.lifeBarWidget:setPercent(percent) + + local color + for i, v in pairs(LifeBarColors) do + if percent > v.percentAbove then + color = v.color + break + end + end + + self.lifeBarWidget:setBackgroundColor(color) +end \ No newline at end of file diff --git a/800OTClient/modules/gamelib/ui/uiitem.lua b/800OTClient/modules/gamelib/ui/uiitem.lua new file mode 100644 index 0000000..05ab126 --- /dev/null +++ b/800OTClient/modules/gamelib/ui/uiitem.lua @@ -0,0 +1,137 @@ +function UIItem:onDragEnter(mousePos) + if self:isVirtual() then return false end + + local item = self:getItem() + if not item then return false end + + self:setBorderWidth(1) + self.currentDragThing = item + g_mouse.pushCursor('target') + return true +end + +function UIItem:onDragLeave(droppedWidget, mousePos) + if self:isVirtual() then return false end + self.currentDragThing = nil + g_mouse.popCursor('target') + self:setBorderWidth(0) + self.hoveredWho = nil + return true +end + +function UIItem:onDrop(widget, mousePos, forced) + if not self:canAcceptDrop(widget, mousePos) and not forced then return false end + + local item = widget.currentDragThing + if not item or not item:isItem() then return false end + + if self.selectable then + if item:isPickupable() then + self:setItem(Item.create(item:getId(), item:getCountOrSubType())) + return true + end + return false + end + + local toPos = self.position + + local itemPos = item:getPosition() + if itemPos.x == toPos.x and itemPos.y == toPos.y and itemPos.z == toPos.z then return false end + + if item:getCount() > 1 then + modules.game_interface.moveStackableItem(item, toPos) + else + g_game.move(item, toPos, 1) + end + + self:setBorderWidth(0) + return true +end + +function UIItem:onDestroy() + if self == g_ui.getDraggingWidget() and self.hoveredWho then + self.hoveredWho:setBorderWidth(0) + end + + if self.hoveredWho then + self.hoveredWho = nil + end +end + +function UIItem:onHoverChange(hovered) + UIWidget.onHoverChange(self, hovered) + + if self:isVirtual() or not self:isDraggable() then return end + + local draggingWidget = g_ui.getDraggingWidget() + if draggingWidget and self ~= draggingWidget then + local gotMap = draggingWidget:getClassName() == 'UIGameMap' + local gotItem = draggingWidget:getClassName() == 'UIItem' and not draggingWidget:isVirtual() + if hovered and (gotItem or gotMap) then + self:setBorderWidth(1) + draggingWidget.hoveredWho = self + else + self:setBorderWidth(0) + draggingWidget.hoveredWho = nil + end + end +end + +function UIItem:onMouseRelease(mousePosition, mouseButton) + if self.cancelNextRelease then + self.cancelNextRelease = false + return true + end + + if self:isVirtual() then return false end + + local item = self:getItem() + if not item or not self:containsPoint(mousePosition) then return false end + + if modules.client_options.getOption('classicControl') and not g_app.isMobile() and + ((g_mouse.isPressed(MouseLeftButton) and mouseButton == MouseRightButton) or + (g_mouse.isPressed(MouseRightButton) and mouseButton == MouseLeftButton)) then + g_game.look(item) + self.cancelNextRelease = true + return true + elseif modules.game_interface.processMouseAction(mousePosition, mouseButton, nil, item, item, nil, nil) then + return true + end + return false +end + +function UIItem:canAcceptDrop(widget, mousePos) + if not self.selectable and (self:isVirtual() or not self:isDraggable()) then return false end + if not widget or not widget.currentDragThing then return false end + + local children = rootWidget:recursiveGetChildrenByPos(mousePos) + for i=1,#children do + local child = children[i] + if child == self then + return true + elseif not child:isPhantom() then + return false + end + end + + error('Widget ' .. self:getId() .. ' not in drop list.') + return false +end + +function UIItem:onClick(mousePos) + if not self.selectable or not self.editable then + return + end + + if modules.game_itemselector then + modules.game_itemselector.show(self) + end +end + +function UIItem:onItemChange() + local tooltip = nil + if self:getItem() and self:getItem():getTooltip():len() > 0 then + tooltip = self:getItem():getTooltip() + end + self:setTooltip(tooltip) +end \ No newline at end of file diff --git a/800OTClient/modules/gamelib/ui/uiminimap.lua b/800OTClient/modules/gamelib/ui/uiminimap.lua new file mode 100644 index 0000000..3672280 --- /dev/null +++ b/800OTClient/modules/gamelib/ui/uiminimap.lua @@ -0,0 +1,316 @@ +function UIMinimap:onCreate() + self.autowalk = true +end + +function UIMinimap:onSetup() + self.flagWindow = nil + self.flags = {} + self.alternatives = {} + self.onAddAutomapFlag = function(pos, icon, description) self:addFlag(pos, icon, description) end + self.onRemoveAutomapFlag = function(pos, icon, description) self:removeFlag(pos, icon, description) end + connect(g_game, { + onAddAutomapFlag = self.onAddAutomapFlag, + onRemoveAutomapFlag = self.onRemoveAutomapFlag, + }) +end + +function UIMinimap:onDestroy() + for _,widget in pairs(self.alternatives) do + widget:destroy() + end + self.alternatives = {} + disconnect(g_game, { + onAddAutomapFlag = self.onAddAutomapFlag, + onRemoveAutomapFlag = self.onRemoveAutomapFlag, + }) + self:destroyFlagWindow() + self.flags = {} +end + +function UIMinimap:onVisibilityChange() + if not self:isVisible() then + self:destroyFlagWindow() + end +end + +function UIMinimap:onCameraPositionChange(cameraPos) + if self.cross then + self:setCrossPosition(self.cross.pos) + end +end + +function UIMinimap:hideFloor() + self.floorUpWidget:hide() + self.floorDownWidget:hide() +end + +function UIMinimap:hideZoom() + self.zoomInWidget:hide() + self.zoomOutWidget:hide() +end + +function UIMinimap:disableAutoWalk() + self.autowalk = false +end + +function UIMinimap:load() + local settings = g_settings.getNode('Minimap') + if settings then + if settings.flags then + for _,flag in pairs(settings.flags) do + self:addFlag(flag.position, flag.icon, flag.description) + end + end + self:setZoom(settings.zoom) + end +end + +function UIMinimap:save() + local settings = { flags={} } + for _,flag in pairs(self.flags) do + if not flag.temporary then + table.insert(settings.flags, { + position = flag.pos, + icon = flag.icon, + description = flag.description, + }) + end + end + settings.zoom = self:getZoom() + g_settings.setNode('Minimap', settings) +end + +local function onFlagMouseRelease(widget, pos, button) + if button == MouseRightButton then + local menu = g_ui.createWidget('PopupMenu') + menu:setGameMenu(true) + menu:addOption(tr('Delete mark'), function() widget:destroy() end) + menu:display(pos) + return true + end + return false +end + +function UIMinimap:setCrossPosition(pos) + local cross = self.cross + if not self.cross then + cross = g_ui.createWidget('MinimapCross', self) + cross:setIcon('/images/game/minimap/cross') + self.cross = cross + end + + pos.z = self:getCameraPosition().z + cross.pos = pos + if pos then + self:centerInPosition(cross, pos) + else + cross:breakAnchors() + end +end + +function UIMinimap:addFlag(pos, icon, description, temporary) + if not pos or not icon then return end + local flag = self:getFlag(pos, icon, description) + if flag or not icon then + return + end + temporary = temporary or false + + flag = g_ui.createWidget('MinimapFlag') + self:insertChild(1, flag) + flag.pos = pos + flag.description = description + flag.icon = icon + flag.temporary = temporary + if type(tonumber(icon)) == 'number' then + flag:setIcon('/images/game/minimap/flag' .. icon) + else + flag:setIcon(resolvepath(icon, 1)) + end + flag:setTooltip(description) + flag.onMouseRelease = onFlagMouseRelease + flag.onDestroy = function() table.removevalue(self.flags, flag) end + table.insert(self.flags, flag) + self:centerInPosition(flag, pos) +end + +function UIMinimap:addAlternativeWidget(widget, pos, maxZoom) + widget.pos = pos + widget.maxZoom = maxZoom or 0 + widget.minZoom = minZoom + table.insert(self.alternatives, widget) +end + +function UIMinimap:setAlternativeWidgetsVisible(show) + local layout = self:getLayout() + layout:disableUpdates() + for _,widget in pairs(self.alternatives) do + if show then + self:insertChild(1, widget) + self:centerInPosition(widget, widget.pos) + else + self:removeChild(widget) + end + end + layout:enableUpdates() + layout:update() +end + +function UIMinimap:onZoomChange(zoom) + for _,widget in pairs(self.alternatives) do + if (not widget.minZoom or widget.minZoom >= zoom) and widget.maxZoom <= zoom then + widget:show() + else + widget:hide() + end + end +end + +function UIMinimap:getFlag(pos) + for _,flag in pairs(self.flags) do + if flag.pos.x == pos.x and flag.pos.y == pos.y and flag.pos.z == pos.z then + return flag + end + end + return nil +end + +function UIMinimap:removeFlag(pos, icon, description) + local flag = self:getFlag(pos) + if flag then + flag:destroy() + end +end + +function UIMinimap:reset() + self:setZoom(0) + if self.cross then + self:setCameraPosition(self.cross.pos) + end +end + +function UIMinimap:move(x, y) + local cameraPos = self:getCameraPosition() + local scale = self:getScale() + if scale > 1 then scale = 1 end + local dx = x/scale + local dy = y/scale + local pos = {x = cameraPos.x - dx, y = cameraPos.y - dy, z = cameraPos.z} + self:setCameraPosition(pos) +end + +function UIMinimap:onMouseWheel(mousePos, direction) + local keyboardModifiers = g_keyboard.getModifiers() + if direction == MouseWheelUp and keyboardModifiers == KeyboardNoModifier then + self:zoomIn() + elseif direction == MouseWheelDown and keyboardModifiers == KeyboardNoModifier then + self:zoomOut() + elseif direction == MouseWheelDown and keyboardModifiers == KeyboardCtrlModifier then + self:floorUp(1) + elseif direction == MouseWheelUp and keyboardModifiers == KeyboardCtrlModifier then + self:floorDown(1) + end +end + +function UIMinimap:onMousePress(pos, button) + if not self:isDragging() then + self.allowNextRelease = true + end +end + +function UIMinimap:onMouseRelease(pos, button) + if not self.allowNextRelease then return true end + self.allowNextRelease = false + + local mapPos = self:getTilePosition(pos) + if not mapPos then return end + + if button == MouseLeftButton then + local player = g_game.getLocalPlayer() + if self.autowalk then + player:autoWalk(mapPos) + end + return true + elseif button == MouseRightButton then + local menu = g_ui.createWidget('PopupMenu') + menu:setGameMenu(true) + menu:addOption(tr('Create mark'), function() self:createFlagWindow(mapPos) end) + menu:display(pos) + return true + end + return false +end + +function UIMinimap:onDragEnter(pos) + self.dragReference = pos + self.dragCameraReference = self:getCameraPosition() + return true +end + +function UIMinimap:onDragMove(pos, moved) + local scale = self:getScale() + local dx = (self.dragReference.x - pos.x)/scale + local dy = (self.dragReference.y - pos.y)/scale + local pos = {x = self.dragCameraReference.x + dx, y = self.dragCameraReference.y + dy, z = self.dragCameraReference.z} + self:setCameraPosition(pos) + return true +end + +function UIMinimap:onDragLeave(widget, pos) + return true +end + +function UIMinimap:onStyleApply(styleName, styleNode) + for name,value in pairs(styleNode) do + if name == 'autowalk' then + self.autowalk = value + end + end +end + +function UIMinimap:createFlagWindow(pos) + if self.flagWindow then return end + if not pos then return end + + self.flagWindow = g_ui.createWidget('MinimapFlagWindow', rootWidget) + + local positionLabel = self.flagWindow:getChildById('position') + local description = self.flagWindow:getChildById('description') + local okButton = self.flagWindow:getChildById('okButton') + local cancelButton = self.flagWindow:getChildById('cancelButton') + + positionLabel:setText(string.format('%i, %i, %i', pos.x, pos.y, pos.z)) + + local flagRadioGroup = UIRadioGroup.create() + for i=0,19 do + local checkbox = self.flagWindow:getChildById('flag' .. i) + checkbox.icon = i + flagRadioGroup:addWidget(checkbox) + end + + flagRadioGroup:selectWidget(flagRadioGroup:getFirstWidget()) + + local successFunc = function() + self:addFlag(pos, flagRadioGroup:getSelectedWidget().icon, description:getText()) + self:destroyFlagWindow() + end + + local cancelFunc = function() + self:destroyFlagWindow() + end + + okButton.onClick = successFunc + cancelButton.onClick = cancelFunc + + self.flagWindow.onEnter = successFunc + self.flagWindow.onEscape = cancelFunc + + self.flagWindow.onDestroy = function() flagRadioGroup:destroy() end +end + +function UIMinimap:destroyFlagWindow() + if self.flagWindow then + self.flagWindow:destroy() + self.flagWindow = nil + end +end diff --git a/800OTClient/modules/gamelib/util.lua b/800OTClient/modules/gamelib/util.lua new file mode 100644 index 0000000..e3abf07 --- /dev/null +++ b/800OTClient/modules/gamelib/util.lua @@ -0,0 +1,11 @@ +function postostring(pos) + return pos.x .. " " .. pos.y .. " " .. pos.z +end + +function dirtostring(dir) + for k,v in pairs(Directions) do + if v == dir then + return k + end + end +end \ No newline at end of file diff --git a/800OTClient/modules/updater/updater.lua b/800OTClient/modules/updater/updater.lua new file mode 100644 index 0000000..5e9aa9e --- /dev/null +++ b/800OTClient/modules/updater/updater.lua @@ -0,0 +1,278 @@ +Updater = { } + +Updater.maxRetries = 5 + +--[[ + +]]-- + +local updaterWindow +local loadModulesFunction +local scheduledEvent +local httpOperationId = 0 + +local function onLog(level, message, time) + if level == LogError then + Updater.error(message) + g_logger.setOnLog(nil) + end +end + +local function initAppWindow() + if g_resources.getLayout() == "mobile" then + g_window.setMinimumSize({ width = 640, height = 360 }) + else + g_window.setMinimumSize({ width = 800, height = 640 }) + end + + -- window size + local size = { width = 1024, height = 600 } + size = g_settings.getSize('window-size', size) + g_window.resize(size) + + -- window position, default is the screen center + local displaySize = g_window.getDisplaySize() + local defaultPos = { x = (displaySize.width - size.width)/2, + y = (displaySize.height - size.height)/2 } + local pos = g_settings.getPoint('window-pos', defaultPos) + pos.x = math.max(pos.x, 0) + pos.y = math.max(pos.y, 0) + g_window.move(pos) + + -- window maximized? + local maximized = g_settings.getBoolean('window-maximized', false) + if maximized then g_window.maximize() end + + g_window.setTitle(g_app.getName()) + g_window.setIcon('/images/clienticon') + + if g_app.isMobile() then + scheduleEvent(function() + g_app.scale(5.0) + end, 10) + end +end + +local function loadModules() + if loadModulesFunction then + local tmpLoadFunc = loadModulesFunction + loadModulesFunction = nil + tmpLoadFunc() + end +end + +local function downloadFiles(url, files, index, retries, doneCallback) + if not updaterWindow then return end + local entry = files[index] + if not entry then -- finished + return doneCallback() + end + local file = entry[1] + local file_checksum = entry[2] + + if retries > 0 then + updaterWindow.downloadStatus:setText(tr("Downloading (%i retry):\n%s", retries, file)) + else + updaterWindow.downloadStatus:setText(tr("Downloading:\n%s", file)) + end + updaterWindow.downloadProgress:setPercent(0) + updaterWindow.mainProgress:setPercent(math.floor(100 * index / #files)) + + httpOperationId = HTTP.download(url .. file, file, + function (file, checksum, err) + if not err and checksum ~= file_checksum then + err = "Invalid checksum of: " .. file .. ".\nShould be " .. file_checksum .. ", is: " .. checksum + end + if err then + if retries >= Updater.maxRetries then + Updater.error("Can't download file: " .. file .. ".\nError: " .. err) + else + scheduledEvent = scheduleEvent(function() + downloadFiles(url, files, index, retries + 1, doneCallback) + end, 250) + end + return + end + downloadFiles(url, files, index + 1, 0, doneCallback) + end, + function (progress, speed) + updaterWindow.downloadProgress:setPercent(progress) + updaterWindow.downloadProgress:setText(speed .. " kbps") + end) +end + +local function updateFiles(data, keepCurrentFiles) + if not updaterWindow then return end + if type(data) ~= "table" then + return Updater.error("Invalid data from updater api (not table)") + end + if type(data["error"]) == 'string' and data["error"]:len() > 0 then + return Updater.error(data["error"]) + end + if not data["files"] or type(data["url"]) ~= 'string' or data["url"]:len() < 4 then + return Updater.error("Invalid data from updater api: " .. json.encode(data, 2)) + end + if data["keepFiles"] then + keepCurrentFiles = true + end + + local newFiles = false + local finalFiles = {} + local localFiles = g_resources.filesChecksums() + local toUpdate = {} + -- keep all files or files from data/things + for file, checksum in pairs(localFiles) do + if keepCurrentFiles or string.find(file, "data/things") then + table.insert(finalFiles, file) + end + end + -- update files + for file, checksum in pairs(data["files"]) do + table.insert(finalFiles, file) + if not localFiles[file] or localFiles[file] ~= checksum then + table.insert(toUpdate, {file, checksum}) + newFiles = true + end + end + -- update binary + local binary = nil + if type(data["binary"]) == "table" and data["binary"]["file"]:len() > 1 then + local selfChecksum = g_resources.selfChecksum() + if selfChecksum:len() > 0 and selfChecksum ~= data["binary"]["checksum"] then + binary = data["binary"]["file"] + table.insert(toUpdate, {binary, data["binary"]["checksum"]}) + end + end + + if #toUpdate == 0 then -- nothing to update + updaterWindow.mainProgress:setPercent(100) + scheduledEvent = scheduleEvent(Updater.abort, 20) + return + end + + -- update of some files require full client restart + local forceRestart = false + local reloadModules = false + local forceRestartPattern = {"init.lua", "corelib", "updater", "otmod"} + for _, file in ipairs(toUpdate) do + for __, pattern in ipairs(forceRestartPattern) do + if string.find(file[1], pattern) then + forceRestart = true + end + if not string.find(file[1], "data/things") then + reloadModules = true + end + end + end + + updaterWindow.status:setText(tr("Updating %i files", #toUpdate)) + updaterWindow.mainProgress:setPercent(0) + updaterWindow.downloadProgress:setPercent(0) + updaterWindow.downloadProgress:show() + updaterWindow.downloadStatus:show() + updaterWindow.changeUrlButton:hide() + downloadFiles(data["url"], toUpdate, 1, 0, function() + updaterWindow.status:setText(tr("Updating client (may take few seconds)")) + updaterWindow.mainProgress:setPercent(100) + updaterWindow.downloadProgress:hide() + updaterWindow.downloadStatus:hide() + scheduledEvent = scheduleEvent(function() + local restart = binary or (not loadModulesFunction and reloadModules) or forceRestart + if newFiles then + g_resources.updateData(finalFiles, not restart) + end + if binary then + g_resources.updateExecutable(binary) + end + if restart then + g_app.restart() + else + if reloadModules then + g_modules.reloadModules() + end + Updater.abort() + end + end, 100) + end) +end + +-- public functions +function Updater.init(loadModulesFunc) + g_logger.setOnLog(onLog) + loadModulesFunction = loadModulesFunc + initAppWindow() + Updater.check() +end + +function Updater.terminate() + loadModulesFunction = nil + Updater.abort() +end + +function Updater.abort() + HTTP.cancel(httpOperationId) + removeEvent(scheduledEvent) + if updaterWindow then + updaterWindow:destroy() + updaterWindow = nil + end + loadModules() +end + +function Updater.check(args) + if updaterWindow then return end + + updaterWindow = g_ui.displayUI('updater') + updaterWindow:show() + updaterWindow:focus() + updaterWindow:raise() + + local updateData = nil + local function progressUpdater(value) + removeEvent(scheduledEvent) + if value == 100 then + return Updater.error(tr("Timeout")) + end + if updateData and (value > 60 or (not g_app.isMobile() or not ALLOW_CUSTOM_SERVERS or not loadModulesFunc)) then -- gives 3s to set custom updater for mobile version + return updateFiles(updateData) + end + scheduledEvent = scheduleEvent(function() progressUpdater(value + 1) end, 50) + updaterWindow.mainProgress:setPercent(value) + end + progressUpdater(0) + + httpOperationId = HTTP.postJSON(Services.updater, { + version = APP_VERSION, + build = g_app.getVersion(), + os = g_app.getOs(), + platform = g_window.getPlatformType(), + args = args or {} + }, function(data, err) + if err then + return Updater.error(err) + end + updateData = data + end) +end + +function Updater.error(message) + removeEvent(scheduledEvent) + if not updaterWindow then return end + displayErrorBox(tr("Updater Error"), message).onOk = function() + Updater.abort() + end +end + +function Updater.changeUrl() + removeEvent(scheduledEvent) + modules.client_textedit.edit(Services.updater, {title="Enter updater url", width=300}, function(newUrl) + if updaterWindow and newUrl:len() > 4 then + Services.updater = newUrl + end + if updaterWindow then + updaterWindow:destroy() + updaterWindow = nil + end + Updater.check() + end) +end \ No newline at end of file diff --git a/800OTClient/modules/updater/updater.otmod b/800OTClient/modules/updater/updater.otmod new file mode 100644 index 0000000..85d4959 --- /dev/null +++ b/800OTClient/modules/updater/updater.otmod @@ -0,0 +1,12 @@ +Module + name: updater + description: Updates client + author: otclient@otclient.ovh + website: otclient.ovh + reloadable: false + scripts: [ updater ] + dependencies: [ client_locales, client_styles, client_textedit ] + @onUnload: Updater.terminate() + + load-later: + - client_background diff --git a/800OTClient/modules/updater/updater.otui b/800OTClient/modules/updater/updater.otui new file mode 100644 index 0000000..ddb9f95 --- /dev/null +++ b/800OTClient/modules/updater/updater.otui @@ -0,0 +1,53 @@ +StaticMainWindow + id: updaterWindow + !text: tr('Updater') + width: 350 + + layout: + type: verticalBox + fit-children: true + + Label + id: status + !text: tr('Checking for updates') + text-align: center + + ProgressBar + id: mainProgress + height: 15 + background-color: #4444ff + margin-bottom: 5 + margin-top: 5 + + Label + id: downloadStatus + text-align: center + text-auto-resize: true + text-wrap: true + visible: false + + ProgressBar + id: downloadProgress + height: 15 + background-color: #4444ff + margin-bottom: 5 + margin-top: 5 + visible: false + + Button + id: changeUrlButton + margin-left: 50 + margin-right: 50 + margin-top: 5 + margin-bottom: 10 + !text: tr('Change updater URL') + @onClick: Updater.changeUrl() + $!mobile: + visible: false + + Button + margin-left: 90 + margin-right: 90 + !text: tr('Cancel') + @onClick: Updater.abort() + diff --git a/800OTClient/otclient_linux b/800OTClient/otclient_linux new file mode 100644 index 0000000..9e09691 Binary files /dev/null and b/800OTClient/otclient_linux differ diff --git a/800OTClient/otclient_mac b/800OTClient/otclient_mac new file mode 100644 index 0000000..1efd3e5 Binary files /dev/null and b/800OTClient/otclient_mac differ diff --git a/800OTClient/otclientv8.apk b/800OTClient/otclientv8.apk new file mode 100644 index 0000000..40f1722 Binary files /dev/null and b/800OTClient/otclientv8.apk differ diff --git a/800OTClient/otclientv8.log b/800OTClient/otclientv8.log new file mode 100644 index 0000000..f66dfc8 --- /dev/null +++ b/800OTClient/otclientv8.log @@ -0,0 +1,429 @@ +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 06 2022 18:32:29 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +ERROR: protected lua call failed: /modules/client_topmenu/topmenu.lua:266: attempt to index local 'data' (a nil value) +stack traceback: + [C]: in function '__index' + /modules/client_topmenu/topmenu.lua:266: in function 'callback' + /modules/corelib/http.lua:172: in function +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +ERROR: protected lua call failed: /modules/client_topmenu/topmenu.lua:266: attempt to index local 'data' (a nil value) +stack traceback: + [C]: in function '__index' + /modules/client_topmenu/topmenu.lua:266: in function 'callback' + /modules/corelib/http.lua:172: in function +ERROR: protected lua call failed: /modules/client_topmenu/topmenu.lua:266: attempt to index local 'data' (a nil value) +stack traceback: + [C]: in function '__index' + /modules/client_topmenu/topmenu.lua:266: in function 'callback' + /modules/corelib/http.lua:172: in function +Login to 127.0.0.1:7172 +ERROR: protected lua call failed: /modules/client_topmenu/topmenu.lua:266: attempt to index local 'data' (a nil value) +stack traceback: + [C]: in function '__index' + /modules/client_topmenu/topmenu.lua:266: in function 'callback' + /modules/corelib/http.lua:172: in function +Login to 127.0.0.1:7172 +ERROR: protected lua call failed: /modules/client_topmenu/topmenu.lua:266: attempt to index local 'data' (a nil value) +stack traceback: + [C]: in function '__index' + /modules/client_topmenu/topmenu.lua:266: in function 'callback' + /modules/corelib/http.lua:172: in function +Login to 127.0.0.1:7172 +ERROR: protected lua call failed: /modules/client_topmenu/topmenu.lua:266: attempt to index local 'data' (a nil value) +stack traceback: + [C]: in function '__index' + /modules/client_topmenu/topmenu.lua:266: in function 'callback' + /modules/corelib/http.lua:172: in function +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 08 2022 13:58:07 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Connecting to: 127.0.0.1:7171 +Connecting to: 127.0.0.1:7171 +Connecting to: 127.0.0.1:7171 +Connecting to: 127.0.0.1:7171 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 12:21:31 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +ERROR: Failed to read dat '/things/800/Tibia.dat': corrupt data (id: 100, category: 0, count: 255, lastAttr: 1)' +ERROR: Failed to read dat '/things/800/Tibia.dat': corrupt data (id: 100, category: 0, count: 255, lastAttr: 1)' +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 12:24:10 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Exiting application.. +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +ERROR: ProtocolGame parse message exception (3635 bytes, 2979 unread, last opcode is 0x64 (100), prev opcode is 0x0b (11)): invalid thing id (0) +Packet has been saved to packet.log, you can use it to find what was wrong. (Protocol: 772) +ERROR: ProtocolGame parse message exception (206 bytes, 101 unread, last opcode is 0x66 (102), prev opcode is 0x6d (109)): invalid thing id (0) +Packet has been saved to packet.log, you can use it to find what was wrong. (Protocol: 772) +ERROR: ProtocolGame parse message exception (320 bytes, 0 unread, last opcode is 0x00 (0), prev opcode is 0xa2 (162)): InputMessage eof reached +Packet has been saved to packet.log, you can use it to find what was wrong. (Protocol: 772) +ERROR: no thing at pos:32345 32218 7, stackpos:1 +at: + [C++]: ?getMappedThing@ProtocolGame@@QAE?AV?$shared_object_ptr@VThing@@@stdext@@ABV?$shared_object_ptr@VInputMessage@@@3@@Z +ERROR: no creature found to move +at: + [C++]: ?parseCreatureMove@ProtocolGame@@AAEXABV?$shared_object_ptr@VInputMessage@@@stdext@@@Z +ERROR: no thing at pos:32345 32219 7, stackpos:1 +at: + [C++]: ?getMappedThing@ProtocolGame@@QAE?AV?$shared_object_ptr@VThing@@@stdext@@ABV?$shared_object_ptr@VInputMessage@@@3@@Z +ERROR: no creature found to move +at: + [C++]: ?parseCreatureMove@ProtocolGame@@AAEXABV?$shared_object_ptr@VInputMessage@@@stdext@@@Z +ERROR: no thing at pos:32345 32220 7, stackpos:1 +at: + [C++]: ?getMappedThing@ProtocolGame@@QAE?AV?$shared_object_ptr@VThing@@@stdext@@ABV?$shared_object_ptr@VInputMessage@@@3@@Z +ERROR: no creature found to move +at: + [C++]: ?parseCreatureMove@ProtocolGame@@AAEXABV?$shared_object_ptr@VInputMessage@@@stdext@@@Z +Login to 127.0.0.1:7172 +ERROR: ProtocolGame parse message exception (4035 bytes, 3404 unread, last opcode is 0x64 (100), prev opcode is 0x0b (11)): invalid thing id (0) +Packet has been saved to packet.log, you can use it to find what was wrong. (Protocol: 772) +ERROR: no thing at pos:32346 32221 7, stackpos:1 +at: + [C++]: ?getMappedThing@ProtocolGame@@QAE?AV?$shared_object_ptr@VThing@@@stdext@@ABV?$shared_object_ptr@VInputMessage@@@3@@Z +ERROR: no creature found to move +at: + [C++]: ?parseCreatureMove@ProtocolGame@@AAEXABV?$shared_object_ptr@VInputMessage@@@stdext@@@Z +ERROR: no thing at pos:32349 32220 7, stackpos:1 +at: + [C++]: ?getMappedThing@ProtocolGame@@QAE?AV?$shared_object_ptr@VThing@@@stdext@@ABV?$shared_object_ptr@VInputMessage@@@3@@Z +ERROR: no creature found to move +at: + [C++]: ?parseCreatureMove@ProtocolGame@@AAEXABV?$shared_object_ptr@VInputMessage@@@stdext@@@Z +ERROR: no thing at pos:32347 32221 7, stackpos:1 +at: + [C++]: ?getMappedThing@ProtocolGame@@QAE?AV?$shared_object_ptr@VThing@@@stdext@@ABV?$shared_object_ptr@VInputMessage@@@3@@Z +ERROR: no creature found to move +at: + [C++]: ?parseCreatureMove@ProtocolGame@@AAEXABV?$shared_object_ptr@VInputMessage@@@stdext@@@Z +Login to 127.0.0.1:7172 +ERROR: lua function callback failed: /modules/game_walking/walking.lua:322: attempt to index local 'toPos' (a nil value) +stack traceback: + [C]: in function '__index' + /modules/game_walking/walking.lua:322: in function 'walk' + /modules/game_walking/walking.lua:259: in function +ERROR: ProtocolGame parse message exception (4208 bytes, 3577 unread, last opcode is 0x64 (100), prev opcode is 0x0b (11)): invalid thing id (0) +Packet has been saved to packet.log, you can use it to find what was wrong. (Protocol: 772) +ERROR: ProtocolGame parse message exception (222 bytes, 0 unread, last opcode is 0x00 (0), prev opcode is 0xa2 (162)): InputMessage eof reached +Packet has been saved to packet.log, you can use it to find what was wrong. (Protocol: 772) +ERROR: no thing at pos:32350 32220 7, stackpos:1 +at: + [C++]: ?getMappedThing@ProtocolGame@@QAE?AV?$shared_object_ptr@VThing@@@stdext@@ABV?$shared_object_ptr@VInputMessage@@@3@@Z +ERROR: no creature found to move +at: + [C++]: ?parseCreatureMove@ProtocolGame@@AAEXABV?$shared_object_ptr@VInputMessage@@@stdext@@@Z +ERROR: ProtocolGame parse message exception (313 bytes, 181 unread, last opcode is 0x65 (101), prev opcode is 0x6d (109)): invalid thing id (0) +Packet has been saved to packet.log, you can use it to find what was wrong. (Protocol: 772) +ERROR: ProtocolGame parse message exception (230 bytes, 91 unread, last opcode is 0x66 (102), prev opcode is 0x6d (109)): invalid thing id (0) +Packet has been saved to packet.log, you can use it to find what was wrong. (Protocol: 772) +ERROR: ProtocolGame parse message exception (105 bytes, 13 unread, last opcode is 0x66 (102), prev opcode is 0x6d (109)): invalid thing id (0) +Packet has been saved to packet.log, you can use it to find what was wrong. (Protocol: 772) +ERROR: ProtocolGame parse message exception (103 bytes, 29 unread, last opcode is 0x66 (102), prev opcode is 0x6d (109)): invalid thing id (0) +Packet has been saved to packet.log, you can use it to find what was wrong. (Protocol: 772) +ERROR: no thing at pos:32368 32219 7, stackpos:1 +at: + [C++]: ?getMappedThing@ProtocolGame@@QAE?AV?$shared_object_ptr@VThing@@@stdext@@ABV?$shared_object_ptr@VInputMessage@@@3@@Z +ERROR: no creature found to move +at: + [C++]: ?parseCreatureMove@ProtocolGame@@AAEXABV?$shared_object_ptr@VInputMessage@@@stdext@@@Z +ERROR: ProtocolGame parse message exception (90 bytes, 57 unread, last opcode is 0x20 (32), prev opcode is 0xa0 (160)): unhandled opcode 32 +Packet has been saved to packet.log, you can use it to find what was wrong. (Protocol: 772) +ERROR: ProtocolGame parse message exception (90 bytes, 57 unread, last opcode is 0x20 (32), prev opcode is 0xa0 (160)): unhandled opcode 32 +Packet has been saved to packet.log, you can use it to find what was wrong. (Protocol: 772) +ERROR: ProtocolGame parse message exception (90 bytes, 57 unread, last opcode is 0x20 (32), prev opcode is 0xa0 (160)): unhandled opcode 32 +Packet has been saved to packet.log, you can use it to find what was wrong. (Protocol: 772) +ERROR: no thing at pos:32368 32218 7, stackpos:1 +at: + [C++]: ?getMappedThing@ProtocolGame@@QAE?AV?$shared_object_ptr@VThing@@@stdext@@ABV?$shared_object_ptr@VInputMessage@@@3@@Z +ERROR: no creature found to move +at: + [C++]: ?parseCreatureMove@ProtocolGame@@AAEXABV?$shared_object_ptr@VInputMessage@@@stdext@@@Z +ERROR: ProtocolGame parse message exception (66 bytes, 33 unread, last opcode is 0x20 (32), prev opcode is 0xa0 (160)): unhandled opcode 32 +Packet has been saved to packet.log, you can use it to find what was wrong. (Protocol: 772) +ERROR: no thing at pos:32369 32218 7, stackpos:1 +at: + [C++]: ?getMappedThing@ProtocolGame@@QAE?AV?$shared_object_ptr@VThing@@@stdext@@ABV?$shared_object_ptr@VInputMessage@@@3@@Z +ERROR: no creature found to move +at: + [C++]: ?parseCreatureMove@ProtocolGame@@AAEXABV?$shared_object_ptr@VInputMessage@@@stdext@@@Z +ERROR: Missing file: 800/Tibia.dat +Missing file: 800/Tibia.spr + +You should open data/things and create directory 800. +In this directory (data/things/800) you should put missing +files (Tibia.dat and Tibia.spr/Tibia.cwm) from correct Tibia version. +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 12:31:16 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 12:31:57 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 12:39:00 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 12:40:17 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 12:47:31 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 12:48:35 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 12:49:23 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 12:50:38 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 12:51:46 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 12:52:17 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 12:53:55 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 12:55:45 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 12:56:13 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 12:56:55 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 12:57:26 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 12:58:55 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 12:59:22 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 13:00:28 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 13:01:26 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 13:07:44 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 13:09:28 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 13:17:50 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 13:19:55 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 13:20:53 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Exiting application.. +GPU Radeon RX 580 Series (ATI Technologies Inc.) +OpenGL 4.6.13596 Compatibility Profile Context 20.10.35.02 27.20.1034.6 +[Atlas] Texture size is: 4096x4096 (max: 16384x16384) +Found work dir at 'C:/Users/erika/source/repos/Sabrehaven/800OTClient/' +== application started at Apr 09 2022 13:22:25 +OTCv8 3.1 rev 163 (dev) made by otclient.net built on Mar 31 2022 for arch x86 +Connecting to: 127.0.0.1:7171 +Login to 127.0.0.1:7172 +Login to 127.0.0.1:7172 diff --git a/800OTClient/packet.log b/800OTClient/packet.log new file mode 100644 index 0000000..09b0f19 --- /dev/null +++ b/800OTClient/packet.log @@ -0,0 +1,70 @@ +ProtocolGame parse message exception (3635 bytes, 2979 unread, last opcode is 0x64 (100), prev opcode is 0x0b (11), proto: 772): invalid thing id (0) +38 0e 2f 0e 0a 00 00 00 10 32 00 01 +0b ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff +64 53 7e e0 7d 07 66 03 00 ff 66 03 a7 09 00 ff 66 03 ff 04 00 ff ad 01 f6 04 2d 0a 00 ff ad 01 f6 04 2d 0a 00 ff ad 01 f6 04 00 ff ad 01 f5 13 00 ff ad 01 fe 04 00 ff 67 00 00 ff 67 00 00 ff 67 00 00 ff 67 00 02 05 5d 0b 00 ff 98 01 00 ff 98 01 00 ff 66 03 00 ff 66 03 a7 09 00 ff 66 03 f7 04 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 f7 04 00 ff 67 00 00 ff 67 00 00 ff 67 00 00 ff 67 00 0a 05 00 ff 98 01 01 05 00 ff 98 01 01 05 00 ff 66 03 00 ff 66 03 00 ff 66 03 f7 04 70 0b 00 ff ad 01 14 09 5f 0b 00 ff ad 01 15 09 cd 0c 10 00 ff ad 01 23 09 00 ff ad 01 14 09 00 ff ad 01 f7 04 00 ff 67 00 00 ff 67 00 00 ff 67 00 00 ff 67 00 c6 06 00 ff 98 01 00 ff 98 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 f7 04 00 ff ad 01 00 ff ad 01 f3 0b 00 ff ad 01 00 ff ad 01 00 ff ad 01 f7 04 00 ff 66 03 33 12 00 ff 66 03 33 12 00 ff 66 03 3a 12 00 ff 67 00 04 05 00 ff 98 01 01 05 00 ff 98 01 c7 06 00 ff 66 03 00 ff 66 03 00 ff 66 03 f9 04 00 ff ad 01 f6 04 00 ff ad 01 f6 13 00 ff ad 01 f6 04 00 ff ad 01 f6 04 00 ff ad 01 fa 04 00 ff 66 03 00 ff 66 03 00 ff 66 03 36 12 00 ff 66 03 33 12 3d 08 00 ff 66 03 33 12 e6 07 00 ff 66 03 33 12 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 61 00 00 00 00 00 00 00 00 10 06 00 45 72 69 6b 61 73 64 02 80 00 6a 44 13 7c 03 ff 00 dc 05 00 00 00 ff 66 03 00 ff 66 03 b3 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 10 05 00 ff 66 03 0e 05 00 ff 66 03 0e 05 00 ff 66 03 00 ff 66 03 00 ff 66 03 15 05 00 ff 66 03 0e 05 00 ff 66 03 0e 05 00 ff 66 03 0e 05 00 ff 66 03 0e 05 5c 0b 00 ff 66 03 0e 05 00 ff 66 03 0e 05 00 ff 66 03 0e 05 00 ff 66 03 3d 08 00 ff 66 03 0f 05 00 ff ad 01 ad 0d 00 ff ad 01 fb 14 00 ff ad 01 00 ff ad 01 00 ff ad 01 0f 05 00 ff ad 01 11 09 ab 0d 00 ff af 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff af 01 00 ff ad 01 11 09 a9 0d 00 ff 66 03 0e 05 00 ff 66 03 12 05 00 ff ad 01 dc 07 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 0f 05 00 ff ad 01 11 09 00 ff ad 01 74 08 00 ff ad 01 74 08 00 ff ad 01 00 ff ad 01 74 08 00 ff ad 01 74 08 00 ff ad 01 11 09 00 ff ad 01 de 09 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 0f 05 00 ff ad 01 11 09 ab 0d 00 ff af 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff af 01 00 ff ad 01 11 09 a9 0d 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 11 05 00 ff ad 01 0e 05 00 ff ad 01 0e 05 00 ff ad 01 0e 05 00 ff ad 01 00 ff ad 01 0e 05 00 ff ad 01 0e 05 00 ff ad 01 0e 05 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 de 09 00 ff ad 01 00 ff ad 01 3e 08 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 ad 0d 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff 9e 01 00 ff ad 01 1d 09 00 ff ad 01 1e 09 00 ff ad 01 1c 09 00 ff ad 01 18 09 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff 9e 01 01 ff c8 06 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 c8 06 03 ff 0a 05 00 ff 98 01 01 05 00 ff 98 01 01 05 00 ff 98 01 66 06 01 ff f7 04 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 f7 04 03 ff 02 05 00 ff 98 01 00 ff 98 01 00 ff 98 01 01 ff f7 04 00 ff 98 01 f6 04 00 ff 98 01 c9 06 00 ff 98 01 f5 13 00 ff 98 01 c9 06 00 ff 98 01 f7 04 fa 04 00 ff c2 01 02 ff 04 05 00 ff 98 01 01 05 00 ff 98 01 01 05 00 ff 98 01 01 05 01 ff 6a 08 b7 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 6e 08 b8 01 c2 01 07 ff c0 01 00 ff 6f 08 ba 01 00 ff 6d 08 ba 01 00 ff 6d 08 ba 01 00 ff 6d 08 6d 08 ba 01 00 ff 6d 08 ba 01 00 ff c1 01 30 ff 8a 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 10 05 85 04 00 ff 8b 04 00 ff 86 04 1e 0d 00 ff 86 04 12 0e 02 00 ff 86 04 00 ff 86 04 c6 0c 00 ff 86 04 d7 0b 04 bb 0b 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 2e 0d 00 ff 86 04 dd 0c 00 ff 86 04 00 ff 86 04 17 05 00 ff 85 04 8b 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 a0 10 53 0d 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 17 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 15 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 5c 0b 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 18 05 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 de 09 00 ff 98 01 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff c4 01 00 ff 98 01 11 09 a9 0d 00 ff 98 01 17 05 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 0f 05 00 ff 98 01 11 09 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 11 09 00 ff 98 01 17 05 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 3e 08 00 ff 98 01 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff c4 01 00 ff 98 01 11 09 a9 0d 00 ff 98 01 17 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 17 05 00 ff 98 01 15 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 18 05 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 11 05 00 ff 98 01 9b 07 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 de 09 00 ff 98 01 17 05 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 17 05 00 ff 81 04 00 ff 80 04 00 ff 80 04 bd 13 00 ff 80 04 82 04 00 ff 80 04 b5 13 00 ff 80 04 00 ff bc 13 02 ff 02 05 00 ff 98 01 9c 07 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 81 04 00 ff 80 04 00 ff 80 04 00 ff 80 04 00 ff 80 04 00 ff 80 04 00 ff bc 13 02 ff 04 05 00 ff 98 01 01 05 00 ff 98 01 01 05 00 ff 98 01 c7 06 00 ff 98 01 01 05 00 ff b8 13 00 ff b9 13 00 ff b9 13 00 ff b9 13 00 ff b9 13 00 ff b9 13 00 ff ba 13 4b ff 83 04 00 ff 82 04 0c ff 81 04 00 ff 80 04 4a 0f 4a 07 0c ff 81 04 00 ff 80 04 33 0f 00 ff 0e 05 00 ff 0e 05 00 ff 0e 05 00 ff 0e 05 00 ff 15 05 00 ff 0e 05 00 ff 0e 05 00 ff 0e 05 00 ff 0e 05 5c 0b 00 ff 0e 05 00 ff 0e 05 00 ff 0e 05 00 ff 13 05 81 04 00 ff 80 04 6f 07 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 de 09 00 ff 98 01 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff c4 01 00 ff 98 01 11 09 a9 0d 00 ff 98 01 0f 05 00 ff 80 04 64 0e 4a 07 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 0f 05 00 ff 98 01 11 09 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 11 09 00 ff 98 01 0f 05 00 ff 80 04 72 0e 40 07 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 3e 08 00 ff 98 01 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff c4 01 00 ff 98 01 11 09 a9 0d 00 ff 98 01 0f 05 00 ff 80 04 40 07 00 ff 98 01 0e 05 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 11 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 16 05 00 ff 80 04 2e 0f 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff b2 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 de 09 00 ff 98 01 0f 05 00 ff 80 04 42 07 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 0f 05 00 ff 80 04 5c 0e 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 ad 0d 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 3e 08 00 ff 98 01 0f 05 00 ff 80 04 1a 07 08 ff 81 04 00 ff 80 04 00 ff 80 04 00 ff 80 04 00 ff 80 04 00 ff 80 04 08 ff b8 13 00 ff b9 13 00 ff b9 13 00 ff b9 13 00 ff b9 13 00 ff b9 13 62 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 02 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 02 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 02 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 02 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 02 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 02 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 02 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 02 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 ff ff ff ff ff ff f2 ff 83 53 7e e0 7d 07 0b 79 01 79 02 78 03 26 0b 79 04 79 05 79 06 79 07 79 08 79 09 78 0a 81 0d a0 f4 01 f4 01 ff ff 68 e3 18 00 30 00 04 34 08 34 08 2b 00 64 20 0d a1 0b 00 0a 00 0a 00 0a 00 2a 00 12 4e 0a 2d 82 ff d7 8d 00 00 00 10 ff 00 a2 00 00 a0 f4 01 f4 01 ff ff 68 e3 18 00 30 00 04 34 08 34 08 2b 00 64 20 0d b4 15 14 00 57 65 6c 63 6f 6d 65 20 74 6f 20 54 69 62 69 61 6e 75 73 21 b4 15 36 00 59 6f 75 72 20 6c 61 73 74 20 76 69 73 69 74 20 6f 6e 20 54 69 62 69 61 6e 75 73 3a 20 46 72 69 20 41 70 72 20 30 38 20 31 38 3a 32 31 3a 34 37 20 32 30 32 32 2e a0 f4 01 f4 01 ff ff 68 e3 18 00 30 00 04 34 08 34 08 2b 00 64 20 0d + +ProtocolGame parse message exception (206 bytes, 101 unread, last opcode is 0x66 (102), prev opcode is 0x6d (109), proto: 772): invalid thing id (0) +d0 00 ca 00 +6d 54 7e d7 7d 07 01 55 7e d7 7d 07 +66 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 02 05 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 0f 05 00 ff ad 01 a6 0b 00 ff ad 01 00 ff ad 01 61 00 00 00 00 00 28 00 00 80 08 00 42 65 6e 6a 61 6d 69 6e 64 01 80 00 74 4f 75 4c 00 ff 00 5a 00 00 00 00 ff ad 01 17 09 00 ff ad 01 00 ff 98 01 05 05 00 ff 98 01 00 ff 98 01 03 05 00 ff 6b 08 b8 01 03 ff 11 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff b9 13 00 ff b9 13 00 ff ba 13 0a ff 0f 05 00 ff be 13 0b ff 8c 04 00 ff 87 04 38 ff + +ProtocolGame parse message exception (320 bytes, 0 unread, last opcode is 0x00 (0), prev opcode is 0xa2 (162), proto: 772): InputMessage eof reached +40 01 3c 01 6d 59 7e d9 7d 07 01 59 7e da 7d 07 67 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 0e 05 00 ff ad 01 11 09 ab 0d 00 ff ad 01 11 09 00 ff ad 01 11 09 ab 0d 00 ff ad 01 0e 05 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 17 05 00 ff ad 01 14 05 00 ff ad 01 11 09 ab 0d 00 ff ad 01 11 09 00 ff ad 01 11 09 ab 0d 02 ff 85 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 0e 05 00 ff c4 01 00 ff 98 01 74 08 00 ff c4 01 00 ff 98 01 0e 05 00 ff 98 01 00 ff 98 01 00 ff b2 01 00 ff 98 01 0e 05 00 ff c4 01 00 ff 98 01 74 08 00 ff c4 01 00 ff 98 01 15 05 04 ff 0e 05 00 ff 98 01 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 0e 05 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 0e 05 00 ff 98 01 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 0e 05 00 ff 80 04 79 07 03 ff 85 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 89 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 88 04 49 ff +a2 00 +00 + +ProtocolGame parse message exception (4035 bytes, 3404 unread, last opcode is 0x64 (100), prev opcode is 0x0b (11), proto: 772): invalid thing id (0) +c8 0f bf 0f 0a 01 00 00 10 32 00 01 +0b ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff +64 59 7e dd 7d 07 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 b3 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 10 05 00 ff 66 03 0e 05 00 ff 66 03 0e 05 00 ff 66 03 00 ff 66 03 00 ff 66 03 15 05 00 ff 66 03 0e 05 00 ff 66 03 0e 05 00 ff 66 03 0e 05 00 ff 66 03 0e 05 5c 0b 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 3d 08 00 ff 66 03 0f 05 00 ff ad 01 ad 0d 00 ff ad 01 fb 14 00 ff ad 01 00 ff ad 01 00 ff ad 01 0f 05 00 ff ad 01 11 09 ab 0d 00 ff af 01 00 ff ad 01 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 10 05 00 ff 66 03 0e 05 00 ff 66 03 12 05 00 ff ad 01 dc 07 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 0f 05 00 ff ad 01 11 09 00 ff ad 01 74 08 00 ff ad 01 74 08 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 0f 05 00 ff ad 01 de 09 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 0f 05 00 ff ad 01 11 09 ab 0d 00 ff af 01 00 ff ad 01 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 0f 05 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 11 05 00 ff ad 01 0e 05 00 ff ad 01 0e 05 00 ff ad 01 0e 05 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 fb 14 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 61 00 00 00 00 00 01 00 00 10 06 00 45 72 69 6b 61 73 64 02 80 00 6a 44 13 7c 03 ff 00 dc 05 00 00 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 0f 05 00 ff ad 01 ad 0d 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 0f 05 00 ff ad 01 1d 09 00 ff ad 01 1e 09 00 ff ad 01 1c 09 00 ff ad 01 18 09 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 0f 05 25 0a 00 ff ad 01 00 ff ad 01 00 ff ad 01 61 00 00 00 00 00 28 00 00 80 08 00 42 65 6e 6a 61 6d 69 6e 64 03 80 00 74 4f 75 4c 00 ff 00 5a 00 00 00 00 ff ad 01 13 09 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 0f 05 00 ff ad 01 a6 0b 00 ff ad 01 00 ff ad 01 00 ff ad 01 17 09 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 17 05 00 ff ad 01 9b 07 00 ff ad 01 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 11 05 00 ff ad 01 0e 05 00 ff ad 01 0e 05 00 ff ad 01 0e 05 00 ff ad 01 0e 05 84 0a 00 ff ad 01 0e 05 00 ff ad 01 0e 05 84 0a 00 ff ad 01 15 05 00 ff ad 01 14 05 00 ff ad 01 0e 05 00 ff ad 01 0e 05 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 32 12 b9 11 00 ff 67 00 b5 11 bd 11 bd 11 bd 11 29 09 f0 14 00 ff af 11 48 0e 00 ff a3 11 36 12 16 09 00 ff a3 11 32 12 3a 12 00 ff eb 06 30 12 61 0e 0d 09 58 0d e0 0b 06 96 0e 01 00 ff eb 06 3a 12 39 12 00 ff ad 01 0f 05 00 ff ad 01 11 09 ab 0d 00 ff af 01 00 ff ad 01 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 32 12 00 ff 67 00 b9 11 00 ff 67 00 bd 11 00 ff a3 11 13 09 00 ff a3 11 32 12 00 ff eb 06 35 12 34 12 36 12 30 12 39 12 00 ff eb 06 34 12 35 12 61 0e 0d 09 d4 0b 04 d4 0b 02 db 0b 32 96 0e 01 00 ff ad 01 0f 05 00 ff ad 01 11 09 00 ff ad 01 74 08 00 ff ad 01 74 08 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 32 12 00 ff 67 00 ba 11 00 ff 67 00 be 11 00 ff a3 11 13 09 00 ff a3 11 32 12 00 ff eb 06 39 12 36 12 61 0e 0d 09 cf 0c 23 0b 96 0e 01 00 ff eb 06 39 12 00 ff ad 01 0f 05 00 ff ad 01 11 09 ab 0d 00 ff af 01 00 ff ad 01 00 ff ad 01 1f ff 8a 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 03 ff 8b 04 00 ff 86 04 1e 0d 00 ff 86 04 12 0e 02 00 ff 86 04 00 ff 86 04 c6 0c 00 ff 86 04 d7 0b 04 bb 0b 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 2e 0d 01 ff 8a 04 00 ff 85 04 00 ff 85 04 8b 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 a0 10 53 0d 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 01 ff 10 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 15 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 5c 0b 00 ff 86 04 0e 05 01 ff 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 de 09 00 ff 98 01 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 01 ff 0f 05 00 ff 98 01 11 09 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 0f 05 00 ff 98 01 11 09 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 74 08 01 ff 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 3e 08 00 ff 98 01 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 01 ff 17 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 17 05 00 ff 98 01 15 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 00 ff 98 01 0e 05 01 ff 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 11 05 00 ff 98 01 9b 07 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 01 ff 0f 05 00 ff 98 01 11 09 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 01 ff 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 ad 0d 00 ff 98 01 00 ff 98 01 00 ff b2 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 01 ff 11 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 15 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 00 ff 98 01 0e 05 04 ff 8b 04 00 ff 86 04 12 0e 01 fe 0d 01 00 ff 86 04 00 ff 86 04 00 ff 86 04 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 04 ff 8b 04 00 ff 86 04 30 0d 00 ff 86 04 00 ff 86 04 00 ff 86 04 0f 05 00 ff 98 01 11 09 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 74 08 04 ff 8b 04 00 ff 86 04 81 0d d9 0c 00 ff 86 04 00 ff 86 04 00 ff 86 04 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 04 ff 8b 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 17 05 00 ff 98 01 15 05 00 ff 98 01 15 05 00 ff 98 01 15 05 00 ff 98 01 15 05 00 ff 98 01 15 05 38 ff 10 05 00 ff 0e 05 00 ff 0e 05 00 ff 0e 05 00 ff 0e 05 00 ff 0e 05 00 ff 0e 05 00 ff 15 05 00 ff 0e 05 00 ff 0e 05 00 ff 0e 05 00 ff 0e 05 5c 0b 00 ff 0e 05 00 ff 0e 05 00 ff 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 de 09 00 ff 98 01 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff c4 01 00 ff 0f 05 00 ff 98 01 11 09 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 0f 05 00 ff 98 01 11 09 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 3e 08 00 ff 98 01 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff c4 01 00 ff 17 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 11 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff b2 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 0f 05 00 ff 98 01 11 09 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 ad 0d 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 11 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 15 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 00 ff 98 01 0e 05 00 ff 98 01 0e 05 07 ff 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff c4 01 07 ff 0f 05 00 ff 98 01 11 09 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 74 08 00 ff 98 01 74 08 07 ff 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff c4 01 07 ff 11 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 02 ff 83 04 00 ff 82 04 00 ff 82 04 00 ff 82 04 00 ff 82 04 00 ff b5 13 00 ff 80 04 30 0f 00 ff 80 04 79 07 00 ff 80 04 79 07 00 ff 80 04 6a 0e 00 ff 80 04 7a 07 00 ff 80 04 31 0f 2a ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 88 04 00 ff 88 04 00 ff 88 04 00 ff 88 04 00 ff 88 04 00 ff 88 04 00 ff 8c 04 88 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 06 ff 8c 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 06 ff 8c 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 06 ff 8c 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 06 ff 8d 04 00 ff 88 04 00 ff 88 04 00 ff 88 04 00 ff 88 04 00 ff 88 04 00 ff 88 04 00 ff 88 04 ff ff ff ff ff ff fe ff 83 59 7e dd 7d 07 0b 79 01 79 02 78 03 26 0b 79 04 79 05 79 06 79 07 79 08 79 09 78 0a 81 0d a0 f4 01 f4 01 ff ff 68 e3 18 00 30 00 04 34 08 34 08 2b 00 64 20 0d a1 0b 00 0a 00 0a 00 0a 00 2a 00 12 4e 0a 2d 82 ff d7 8d 01 00 00 10 ff 00 a2 00 00 a0 f4 01 f4 01 ff ff 68 e3 18 00 30 00 04 34 08 34 08 2b 00 64 20 0d b4 15 14 00 57 65 6c 63 6f 6d 65 20 74 6f 20 54 69 62 69 61 6e 75 73 21 b4 15 36 00 59 6f 75 72 20 6c 61 73 74 20 76 69 73 69 74 20 6f 6e 20 54 69 62 69 61 6e 75 73 3a 20 53 61 74 20 41 70 72 20 30 39 20 31 32 3a 32 38 3a 34 30 20 32 30 32 32 2e + +ProtocolGame parse message exception (4208 bytes, 3577 unread, last opcode is 0x64 (100), prev opcode is 0x0b (11), proto: 772): invalid thing id (0) +70 10 6c 10 0a 02 00 00 10 32 00 01 +0b ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff +64 5a 7e dd 7d 07 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 b3 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 10 05 00 ff 66 03 0e 05 00 ff 66 03 0e 05 00 ff 66 03 00 ff 66 03 00 ff 66 03 15 05 00 ff 66 03 0e 05 00 ff 66 03 0e 05 00 ff 66 03 0e 05 00 ff 66 03 0e 05 5c 0b 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 3d 08 00 ff 66 03 0f 05 00 ff ad 01 ad 0d 00 ff ad 01 fb 14 00 ff ad 01 00 ff ad 01 00 ff ad 01 0f 05 00 ff ad 01 11 09 ab 0d 00 ff af 01 00 ff ad 01 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 10 05 00 ff 66 03 0e 05 00 ff 66 03 12 05 00 ff ad 01 dc 07 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 0f 05 00 ff ad 01 11 09 00 ff ad 01 74 08 00 ff ad 01 74 08 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 0f 05 00 ff ad 01 de 09 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 0f 05 00 ff ad 01 11 09 ab 0d 00 ff af 01 00 ff ad 01 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 0f 05 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 11 05 00 ff ad 01 0e 05 00 ff ad 01 0e 05 00 ff ad 01 0e 05 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 fb 14 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 61 00 00 00 00 00 02 00 00 10 06 00 45 72 69 6b 61 73 64 02 80 00 6a 44 13 7c 03 ff 00 dc 05 00 00 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 0f 05 00 ff ad 01 ad 0d 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 0f 05 00 ff ad 01 1d 09 00 ff ad 01 1e 09 00 ff ad 01 1c 09 00 ff ad 01 18 09 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 0f 05 25 0a 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 13 09 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 0f 05 00 ff ad 01 a6 0b 00 ff ad 01 00 ff ad 01 61 00 00 00 00 00 28 00 00 80 08 00 42 65 6e 6a 61 6d 69 6e 64 01 80 00 74 4f 75 4c 00 ff 00 5a 00 00 00 00 ff ad 01 17 09 00 ff ad 01 00 ff ad 01 00 ff ad 01 00 ff ad 01 17 05 00 ff ad 01 9b 07 00 ff ad 01 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 11 05 00 ff ad 01 0e 05 00 ff ad 01 0e 05 00 ff ad 01 0e 05 00 ff ad 01 0e 05 84 0a 00 ff ad 01 0e 05 00 ff ad 01 0e 05 84 0a 00 ff ad 01 15 05 00 ff ad 01 14 05 00 ff ad 01 0e 05 00 ff ad 01 0e 05 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 32 12 b9 11 00 ff 67 00 b5 11 bd 11 bd 11 bd 11 29 09 f0 14 00 ff af 11 48 0e 00 ff a3 11 36 12 16 09 00 ff a3 11 32 12 3a 12 00 ff eb 06 30 12 61 0e 0d 09 58 0d e0 0b 06 96 0e 01 00 ff eb 06 3a 12 39 12 00 ff ad 01 0f 05 00 ff ad 01 11 09 ab 0d 00 ff af 01 00 ff ad 01 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 32 12 00 ff 67 00 b9 11 00 ff 67 00 bd 11 00 ff a3 11 13 09 00 ff a3 11 32 12 00 ff eb 06 35 12 34 12 36 12 30 12 39 12 00 ff eb 06 34 12 35 12 61 0e 0d 09 d4 0b 04 d4 0b 02 db 0b 32 96 0e 01 00 ff ad 01 0f 05 00 ff ad 01 11 09 00 ff ad 01 74 08 00 ff ad 01 74 08 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 32 12 00 ff 67 00 ba 11 00 ff 67 00 be 11 00 ff a3 11 13 09 00 ff a3 11 32 12 00 ff eb 06 39 12 36 12 61 0e 0d 09 cf 0c 23 0b 96 0e 01 00 ff eb 06 39 12 00 ff ad 01 0f 05 00 ff ad 01 11 09 ab 0d 00 ff af 01 00 ff ad 01 00 ff ad 01 00 ff 66 03 00 ff 66 03 00 ff 66 03 32 12 ba 11 00 ff 67 00 b5 11 be 11 00 ff a5 11 48 0e 00 ff a3 11 37 12 17 09 00 ff a3 11 3b 12 00 ff eb 06 38 12 00 ff eb 06 61 0e 0d 09 d0 0c 24 0b 96 0e 01 00 ff 67 00 17 05 00 ff ad 01 15 05 00 ff ad 01 15 05 00 ff ad 01 15 05 00 ff ad 01 15 05 11 ff 8a 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 03 ff 8b 04 00 ff 86 04 1e 0d 00 ff 86 04 12 0e 02 00 ff 86 04 00 ff 86 04 c6 0c 00 ff 86 04 d7 0b 04 bb 0b 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 2e 0d 01 ff 8a 04 00 ff 85 04 00 ff 85 04 8b 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 a0 10 53 0d 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 01 ff 10 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 15 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 00 ff 86 04 0e 05 5c 0b 00 ff 86 04 0e 05 01 ff 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 de 09 00 ff 98 01 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 01 ff 0f 05 00 ff 98 01 11 09 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 0f 05 00 ff 98 01 11 09 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 74 08 01 ff 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 3e 08 00 ff 98 01 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 01 ff 17 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 17 05 00 ff 98 01 15 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 00 ff 98 01 0e 05 01 ff 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 11 05 00 ff 98 01 9b 07 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 01 ff 0f 05 00 ff 98 01 11 09 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 01 ff 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 ad 0d 00 ff 98 01 00 ff 98 01 00 ff b2 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 01 ff 11 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 15 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 00 ff 98 01 0e 05 04 ff 8b 04 00 ff 86 04 12 0e 01 fe 0d 01 00 ff 86 04 00 ff 86 04 00 ff 86 04 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 04 ff 8b 04 00 ff 86 04 30 0d 00 ff 86 04 00 ff 86 04 00 ff 86 04 0f 05 00 ff 98 01 11 09 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 74 08 04 ff 8b 04 00 ff 86 04 81 0d d9 0c 00 ff 86 04 00 ff 86 04 00 ff 86 04 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 04 ff 8b 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 17 05 00 ff 98 01 15 05 00 ff 98 01 15 05 00 ff 98 01 15 05 00 ff 98 01 15 05 00 ff 98 01 15 05 01 ff bf 01 00 ff 6c 08 6c 08 b9 01 00 ff 10 05 6c 08 00 ff 13 05 00 ff 86 04 13 05 00 ff 86 04 cb 06 00 ff 13 05 00 ff 16 05 5c 0b 00 ff 13 05 00 ff 13 05 00 ff 13 05 00 ff 16 05 00 ff 13 05 2a ff 10 05 00 ff 0e 05 00 ff 0e 05 00 ff 0e 05 00 ff 0e 05 00 ff 0e 05 00 ff 0e 05 00 ff 15 05 00 ff 0e 05 00 ff 0e 05 00 ff 0e 05 00 ff 0e 05 5c 0b 00 ff 0e 05 00 ff 0e 05 00 ff 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 de 09 00 ff 98 01 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff c4 01 00 ff 0f 05 00 ff 98 01 11 09 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 0f 05 00 ff 98 01 11 09 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 3e 08 00 ff 98 01 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff c4 01 00 ff 17 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 11 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff b2 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 0f 05 00 ff 98 01 11 09 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 ad 0d 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 11 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 15 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 00 ff 98 01 0e 05 00 ff 98 01 0e 05 07 ff 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff c4 01 07 ff 0f 05 00 ff 98 01 11 09 00 ff 98 01 74 08 00 ff 98 01 74 08 00 ff 98 01 00 ff 98 01 74 08 00 ff 98 01 74 08 07 ff 0f 05 00 ff 98 01 11 09 ab 0d 00 ff c4 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff c4 01 07 ff 11 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 00 ff 98 01 0e 05 02 ff 83 04 00 ff 82 04 00 ff 82 04 00 ff 82 04 00 ff 82 04 00 ff b5 13 00 ff 80 04 30 0f 00 ff 80 04 79 07 00 ff 80 04 79 07 00 ff 80 04 6a 0e 00 ff 80 04 7a 07 00 ff 80 04 31 0f 02 ff 81 04 00 ff 80 04 66 0e 00 ff 80 04 00 ff 80 04 65 0e 00 ff 80 04 78 07 00 ff 80 04 64 0e 00 ff 80 04 42 07 00 ff 80 04 1c 07 00 ff 80 04 1d 07 00 ff 80 04 6e 0e 00 ff 80 04 72 0e 00 ff 80 04 79 07 1c ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 85 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 86 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 89 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 88 04 00 ff 88 04 00 ff 88 04 00 ff 88 04 00 ff 88 04 00 ff 88 04 00 ff 8c 04 88 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 06 ff 8c 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 06 ff 8c 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 06 ff 8c 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 00 ff 87 04 06 ff 8d 04 00 ff 88 04 00 ff 88 04 00 ff 88 04 00 ff 88 04 00 ff 88 04 00 ff 88 04 00 ff 88 04 ff ff ff ff ff ff ff ff 0c ff 83 5a 7e dd 7d 07 0b 79 01 79 02 78 03 26 0b 79 04 79 05 79 06 79 07 79 08 79 09 78 0a 81 0d a0 f4 01 f4 01 ff ff 68 e3 18 00 30 00 04 34 08 34 08 2b 00 64 20 0d a1 0b 00 0a 00 0a 00 0a 00 2a 00 12 4e 0a 2d 82 ff d7 8d 02 00 00 10 ff 00 a2 00 00 a0 f4 01 f4 01 ff ff 68 e3 18 00 30 00 04 34 08 34 08 2b 00 64 20 0d b4 15 14 00 57 65 6c 63 6f 6d 65 20 74 6f 20 54 69 62 69 61 6e 75 73 21 b4 15 36 00 59 6f 75 72 20 6c 61 73 74 20 76 69 73 69 74 20 6f 6e 20 54 69 62 69 61 6e 75 73 3a 20 53 61 74 20 41 70 72 20 30 39 20 31 32 3a 32 38 3a 35 31 20 32 30 32 32 2e + +ProtocolGame parse message exception (222 bytes, 0 unread, last opcode is 0x00 (0), prev opcode is 0xa2 (162), proto: 772): InputMessage eof reached +e0 00 da 00 6d 5a 7e da 7d 07 01 5a 7e d9 7d 07 65 98 01 00 ff 98 01 1c 09 00 ff 98 01 00 ff 98 01 00 ff 98 01 01 05 00 ff 66 03 00 ff 66 03 00 ff 66 03 01 05 5b 0b 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 08 05 00 ff 67 00 00 ff 67 00 fb 04 9c 0a 00 ff fc 03 f7 04 a0 0a 9f 0a a0 0a 00 ff fc 03 fa 04 00 ff 80 04 00 ff 80 04 00 ff 80 04 00 ff 80 04 00 ff b9 13 01 ff 01 05 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 03 05 00 ff 6d 08 ba 01 00 ff 6c 08 b9 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 05 ff be 13 00 ff bc 13 00 ff bc 13 00 ff bc 13 00 ff bc 13 00 ff bc 13 00 ff bc 13 00 ff ba 13 5f ff +a2 00 +00 + +ProtocolGame parse message exception (313 bytes, 181 unread, last opcode is 0x65 (101), prev opcode is 0x6d (109), proto: 772): invalid thing id (0) +38 01 35 01 +6d 5a 7e d8 7d 07 01 5a 7e d7 7d 07 +65 98 01 00 ff 98 01 15 09 00 ff 98 01 00 ff 98 01 00 ff 98 01 01 05 00 ff 66 03 00 ff 66 03 01 05 00 ff 98 01 de 09 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 08 05 00 ff 67 00 ee 11 00 ff 67 00 f6 04 00 ff fc 03 ac 12 61 00 00 00 00 00 98 00 00 80 05 00 46 72 6f 64 6f 64 03 80 00 3a 44 6d 83 00 ff 00 5a 00 00 00 00 ff fc 03 b0 12 00 ff 80 04 00 ff 80 04 00 ff 80 04 00 ff 80 04 00 ff b9 13 00 ff 06 05 00 ff 98 01 0a 05 00 ff 98 01 02 05 00 ff 98 01 c6 06 00 ff 98 01 02 05 00 ff 98 01 09 05 00 ff 98 01 68 06 00 ff 98 01 05 05 00 ff c0 01 00 ff f6 04 00 ff 98 01 00 ff 98 01 f8 04 00 ff 98 01 c8 06 04 ff be 13 00 ff bd 13 00 ff 80 04 00 ff 80 04 00 ff 80 04 00 ff 80 04 00 ff 80 04 00 ff 80 04 00 ff b9 13 00 ff f6 04 00 ff 98 01 f7 04 00 ff 98 01 fa 04 01 ff f8 04 59 06 59 06 58 06 82 04 0c ff be 13 00 ff bc 13 00 ff bc 13 01 ff be 13 00 ff ba 13 48 ff + +ProtocolGame parse message exception (230 bytes, 91 unread, last opcode is 0x66 (102), prev opcode is 0x6d (109), proto: 772): invalid thing id (0) +e8 00 e2 00 +6d 5f 7e d6 7d 07 01 60 7e d6 7d 07 +66 e6 01 30 12 12 09 00 ff e6 01 36 12 12 09 00 ff e6 01 3a 12 f7 04 00 ff e6 01 35 12 30 12 f7 04 87 0a 00 ff e6 01 0e 09 be 0f 00 ff e6 01 f7 04 00 ff 66 03 30 12 00 ff 66 03 00 ff 66 03 00 ff 66 03 0f 05 00 ff 98 01 00 ff 98 01 00 ff 98 01 00 ff 98 01 84 09 00 ff 98 01 61 00 00 00 00 00 73 01 00 80 04 00 54 6f 64 64 64 02 80 00 73 00 43 72 00 ff 00 5a 00 00 00 00 ff 98 01 00 ff 98 01 f7 04 00 ff 98 01 00 ff 98 01 00 ff 6b 08 b8 01 02 ff 6a 08 b7 01 00 ff 98 01 a5 0b 00 ff 98 01 11 05 00 ff 98 01 00 ff 98 01 85 09 00 ff 98 01 00 ff 80 04 00 ff 80 04 00 ff bc 13 06 ff 81 04 00 ff 80 04 00 ff 80 04 6a 0e 00 ff 80 04 00 ff 80 04 46 ff + +ProtocolGame parse message exception (105 bytes, 13 unread, last opcode is 0x66 (102), prev opcode is 0x6d (109), proto: 772): invalid thing id (0) +68 00 65 00 +6d 66 7e d6 7d 07 02 67 7e d6 7d 07 +66 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 61 00 00 00 00 00 17 00 00 80 05 00 41 72 75 64 61 64 02 8c 00 4d 53 4f 5f 00 ff 00 5a 00 00 00 00 ff 66 03 00 ff 66 03 00 ff 66 03 62 ff + +ProtocolGame parse message exception (103 bytes, 29 unread, last opcode is 0x66 (102), prev opcode is 0x6d (109), proto: 772): invalid thing id (0) +68 00 63 00 +6d 67 7e d6 7d 07 02 68 7e d6 7d 07 +66 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 61 00 00 00 00 00 1b 5e 00 40 03 00 44 6f 67 64 00 20 00 00 00 00 00 00 ff 00 7c 00 00 00 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 00 ff 66 03 62 ff + +ProtocolGame parse message exception (90 bytes, 57 unread, last opcode is 0x20 (32), prev opcode is 0xa0 (160), proto: 772): unhandled opcode 32 +58 00 56 00 83 6c 7e d6 7d 07 0d +a0 f4 01 f4 01 ff ff 68 e3 18 00 30 00 04 34 08 34 08 2b 00 64 +20 0d a0 f4 01 f4 01 ff ff 68 e3 18 00 30 00 04 34 08 34 08 2b 00 64 20 0d aa 01 00 00 00 06 00 45 72 69 6b 61 73 30 00 01 6c 7e d6 7d 07 0a 00 65 78 75 72 61 20 67 72 61 6e + +ProtocolGame parse message exception (90 bytes, 57 unread, last opcode is 0x20 (32), prev opcode is 0xa0 (160), proto: 772): unhandled opcode 32 +58 00 56 00 83 6c 7e d6 7d 07 0d +a0 f4 01 f4 01 ff ff 68 e3 18 00 30 00 04 34 08 34 08 2b 00 64 +20 0d a0 f4 01 f4 01 ff ff 68 e3 18 00 30 00 04 34 08 34 08 2b 00 64 20 0d aa 02 00 00 00 06 00 45 72 69 6b 61 73 30 00 01 6c 7e d6 7d 07 0a 00 65 78 75 72 61 20 67 72 61 6e + +ProtocolGame parse message exception (90 bytes, 57 unread, last opcode is 0x20 (32), prev opcode is 0xa0 (160), proto: 772): unhandled opcode 32 +58 00 56 00 83 6c 7e d6 7d 07 0d +a0 f4 01 f4 01 ff ff 68 e3 18 00 30 00 04 34 08 34 08 2b 00 64 +20 0d a0 f4 01 f4 01 ff ff 68 e3 18 00 30 00 04 34 08 34 08 2b 00 64 20 0d aa 03 00 00 00 06 00 45 72 69 6b 61 73 30 00 01 6c 7e d6 7d 07 0a 00 65 78 75 72 61 20 76 69 74 61 + +ProtocolGame parse message exception (66 bytes, 33 unread, last opcode is 0x20 (32), prev opcode is 0xa0 (160), proto: 772): unhandled opcode 32 +40 00 3e 00 83 6d 7e d6 7d 07 0b +a0 f4 01 f4 01 ff ff 68 e3 18 00 30 00 04 34 08 34 08 2b 00 64 +20 0d aa 04 00 00 00 06 00 45 72 69 6b 61 73 30 00 01 6c 7e d6 7d 07 09 00 65 78 6f 72 69 20 76 69 73 + diff --git a/data/items800/items.srv b/data/items800/items.srv new file mode 100644 index 0000000..a9da0a2 --- /dev/null +++ b/data/items800/items.srv @@ -0,0 +1,33804 @@ +# items.srv - Tibia Item definitions +# --- begin of server specific object types --- + +TypeID = 1 +Name = "water" + +TypeID = 2 +Name = "wine" + +TypeID = 3 +Name = "beer" + +TypeID = 4 +Name = "mud" + +TypeID = 5 +Name = "blood" + +TypeID = 6 +Name = "slime" + +TypeID = 7 +Name = "oil" + +TypeID = 8 +Name = "urine" + +TypeID = 9 +Name = "milk" + +TypeID = 10 +Name = "manafluid" + +TypeID = 11 +Name = "lifefluid" + +TypeID = 12 +Name = "lemonade" + +TypeID = 13 +Name = "rum" + +TypeID = 14 +Name = "coconut milk" + +TypeID = 15 +Name = "fruit juice" + +# --- end of server specific object types --- + +TypeID = 100 +Name = "void" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 101 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 102 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 103 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 104 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 105 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 106 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 107 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 108 +Name = "flowers" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 109 +Name = "flowers" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 110 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 111 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 112 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 113 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 114 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 115 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 116 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 117 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 118 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 119 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 120 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 121 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 122 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 123 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 124 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 125 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 126 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 127 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 128 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 129 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 130 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 131 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 132 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 133 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 134 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 135 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 136 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 137 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 138 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 139 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 140 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 141 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 142 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 143 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 144 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 145 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 146 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 147 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 148 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 149 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 150 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=125} + +TypeID = 151 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=125} + +TypeID = 152 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=125} + +TypeID = 153 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=125} + +TypeID = 154 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 155 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 156 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 157 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 158 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 159 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 160 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 161 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 162 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 163 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 164 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 165 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 166 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 167 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 168 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 169 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 170 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 171 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 172 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 173 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 174 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 175 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 176 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 177 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 178 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 179 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 180 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 181 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 182 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 183 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 184 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 185 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 186 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 187 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 188 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 189 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 190 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 191 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 192 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 193 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 194 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 195 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 196 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 197 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 198 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 199 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 200 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 201 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 202 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 203 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 204 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 205 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 206 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 207 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 208 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 209 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 210 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 211 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 212 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 213 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 214 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 215 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 216 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 217 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 218 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 219 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 220 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 221 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 222 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 223 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 224 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 225 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 226 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 227 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 228 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 229 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 230 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 231 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 232 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 233 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 234 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 235 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 236 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 237 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 238 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 239 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 240 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 241 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 242 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 243 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 244 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 245 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 246 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 247 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 248 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 249 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 250 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 251 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 252 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 253 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 254 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 255 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 256 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 257 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 258 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 259 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 260 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 261 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 262 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 263 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 264 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 265 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 266 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 267 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 268 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 269 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 270 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 271 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 272 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 273 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 274 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 275 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 276 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 277 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 278 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 279 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 280 +Name = "dirt floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 281 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 282 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 283 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 284 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 285 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 286 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 287 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 288 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 289 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 290 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 291 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 292 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 293 +Name = "grass" +Flags = {Bank,,CollisionEvent,Unmove} +Attributes = {Waypoints=150} + +TypeID = 294 +Name = "a pitfall" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=150,ExpireTarget=293,TotalExpireTime=300} + +TypeID = 295 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 296 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 297 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 298 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 299 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 300 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 301 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 302 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 303 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 304 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 305 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 306 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 307 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 308 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 309 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 310 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 311 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 312 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 313 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 314 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 315 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 316 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 317 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 318 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 319 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 320 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 321 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 322 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 323 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 324 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 325 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 326 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 327 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 328 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 329 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 330 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 331 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 332 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 333 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 334 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 335 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 336 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 337 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 338 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 339 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 340 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 341 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 342 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 343 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 344 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 345 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 346 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 347 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 348 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 349 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 350 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 351 +Name = "dirt floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 352 +Name = "dirt floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 353 +Name = "dirt floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 354 +Name = "muddy floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 355 +Name = "muddy floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200,FluidSource=MUD} + +TypeID = 356 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 357 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 358 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 359 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 360 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 361 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 362 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 363 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 364 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 365 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 366 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 367 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 368 +Name = "earth ground" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 369 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=110} + +TypeID = 370 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=110} + +TypeID = 371 +Name = "dirt floor" +Flags = {Bank,CollisionEvent,SeparationEvent,Unmove,Disguise} +Attributes = {Waypoints=140,DisguiseTarget=353} + +TypeID = 372 +Name = "muddy floor" +Flags = {Bank,UseEvent,Unmove,Disguise} +Attributes = {Waypoints=200,FluidSource=MUD,DisguiseTarget=355} + +TypeID = 373 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 374 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 375 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 376 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 377 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 378 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 379 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 380 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 381 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 382 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 383 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 384 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 385 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=130} + +TypeID = 386 +Name = "dirt floor" +Description = "There is a hole in the ceiling" +Flags = {Bank,UseEvent,ForceUse,Unmove} +Attributes = {Waypoints=120} + +TypeID = 387 +Name = "a small hole" +Description = "It seems too narrow to climb through" +Flags = {Bank,Unmove,Avoid} +Attributes = {Waypoints=130} + +TypeID = 388 +Name = "stalagmites" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 389 +Name = "stalagmites" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 390 +Name = "a lava hole" +Description = "It seems to be inactive" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 391 +Name = "a lava hole" +Description = "It emits heat and light" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=3,LightColor=215} + +TypeID = 392 +Name = "stalagmites" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 393 +Name = "stalagmites" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 394 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=110,ExpireTarget=372,TotalExpireTime=300} + +TypeID = 395 +Name = "dirt floor" +Flags = {Bank,SeparationEvent,Unmove,Disguise} +Attributes = {Waypoints=140,DisguiseTarget=353} + +TypeID = 396 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 397 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 398 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 399 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 400 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 401 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 402 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 403 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 404 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 405 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 406 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 407 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 408 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 409 +Name = "white marble floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 410 +Name = "black marble floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 411 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 412 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 413 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 414 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 415 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 416 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 417 +Name = "tiled floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 418 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 419 +Name = "a stone tile" +Description = "It seems to be a switch" +Flags = {Bank,CollisionEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 420 +Name = "a stone tile" +Description = "It seems to be a switch" +Flags = {Bank,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 421 +Name = "sandy floor" +Description = "There is a hole in the ceiling" +Flags = {Bank,UseEvent,ForceUse,Unmove} +Attributes = {Waypoints=100} + +TypeID = 422 +Name = "a sandstone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 423 +Name = "tiled floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 424 +Name = "sandstone floor" +Flags = {Bank,Unmove,Avoid} +Attributes = {Waypoints=70} + +TypeID = 425 +Name = "sandstone floor" +Flags = {Bank,Unmove,Avoid} +Attributes = {Waypoints=70} + +TypeID = 426 +Name = "sandstone floor" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay,Disguise} +Attributes = {Waypoints=10,DisguiseTarget=424} + +TypeID = 427 +Name = "sandstone floor" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay,Disguise} +Attributes = {Waypoints=10,DisguiseTarget=425} + +TypeID = 428 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 429 +Name = "a stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 430 +Name = "a stone tile" +Description = "It seems to be a switch" +Flags = {Bank,CollisionEvent,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 431 +Name = "a stone tile" +Description = "It seems to be a switch" +Flags = {Bank,CollisionEvent,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 432 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 433 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 434 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 435 +Name = "a sewer grate" +Flags = {UseEvent,Unmove} + +TypeID = 436 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 437 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 438 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 439 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 440 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 441 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 442 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 443 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 444 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 445 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 446 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 447 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 448 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 449 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 450 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 451 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 452 +Name = "wooden floor" +Description = "It seems to be a switch" +Flags = {Bank,CollisionEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 453 +Name = "wooden floor" +Description = "It seems to be a switch" +Flags = {Bank,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 454 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 455 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 456 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 457 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 458 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 459 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 460 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 461 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 462 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 463 +Name = "a white stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 464 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 465 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 466 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 467 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 468 +Name = "nothing special" +Flags = {Bank,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=470} + +TypeID = 469 +Name = "stairs" +Flags = {Bank,Unmove,Avoid} +Attributes = {Waypoints=95} + +TypeID = 470 +Name = "nothing special" +Flags = {Bank,Unmove} +Attributes = {Waypoints=95} + +TypeID = 471 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 472 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 473 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 474 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 475 +Name = "a closed trapdoor" +Flags = {Bank,CollisionEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 476 +Name = "an open trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Expire} +Attributes = {Waypoints=100,ExpireTarget=475,TotalExpireTime=2} + +TypeID = 477 +Name = "a pedestal" +Flags = {Unmove,Avoid,Height} + +TypeID = 478 +Name = "a sandstone wall" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 479 +Name = "stone floor" +Flags = {Bank,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 480 +Name = "a sandstone wall" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 481 +Name = "stone floor" +Flags = {Bank,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 482 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 483 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 484 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 485 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 486 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 487 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 488 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 489 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 490 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 491 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 492 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 493 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 494 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 495 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 496 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 497 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 498 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 499 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 500 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 501 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 502 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 503 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 504 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 505 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 506 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 507 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 508 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 509 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 510 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 511 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 512 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 513 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 514 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 515 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 516 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 517 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 518 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 519 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 520 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 521 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 522 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 523 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 524 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 525 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 526 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 527 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 528 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 529 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 530 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 531 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 532 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 533 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 534 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 535 +Name = "stone floor" +Flags = {Clip,Unmove} + +TypeID = 536 +Name = "stone floor" +Flags = {Clip,Unmove} + +TypeID = 537 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 538 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 539 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 540 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 541 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 542 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 543 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 544 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 545 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 546 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 547 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 548 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 549 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 550 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 551 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 552 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 553 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 554 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 555 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 556 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 557 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 558 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 559 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 560 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 561 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 562 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 563 +Name = "wooden floor" +Description = "It seems to be a switch" +Flags = {Bank,CollisionEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 564 +Name = "wooden floor" +Description = "It seems to be a switch" +Flags = {Bank,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 565 +Name = "stone floor" +Flags = {Clip,Unmove} + +TypeID = 566 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 567 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 568 +Name = "stone floor" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 569 +Name = "stone floor" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 570 +Name = "stone floor" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 571 +Name = "wooden floor" +Flags = {Unmove} + +TypeID = 572 +Name = "wooden floor" +Flags = {Unmove} + +TypeID = 573 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 574 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 575 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 576 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 577 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 578 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 579 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 580 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 581 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 582 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 583 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 584 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 585 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 586 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 587 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 588 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 589 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 590 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 591 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 592 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 593 +Name = "a loose stone pile" +Flags = {Bank,UseEvent,Unmove,Avoid} +Attributes = {Waypoints=160} + +TypeID = 594 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=160,ExpireTarget=593,TotalExpireTime=300} + +TypeID = 595 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 596 +Name = "a strange carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 597 +Name = "a strange carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 598 +Name = "a strange carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 599 +Name = "a strange carving" +Flags = {Bank,CollisionEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 600 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 601 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 602 +Name = "a ramp" +Flags = {Clip,Unmove} + +TypeID = 603 +Name = "a ramp" +Flags = {Clip,Unmove} + +TypeID = 604 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 605 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 606 +Name = "a loose stone pile" +Flags = {Bank,UseEvent,Unmove,Avoid} +Attributes = {Waypoints=170} + +TypeID = 607 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=170,ExpireTarget=606,TotalExpireTime=300} + +TypeID = 608 +Name = "a loose ice pile" +Flags = {Bank,UseEvent,Unmove,Avoid} +Attributes = {Waypoints=120} + +TypeID = 609 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=120,ExpireTarget=608,TotalExpireTime=300} + +TypeID = 610 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 611 +Name = "a snow heap" +Flags = {UseEvent,Unmove,Avoid} + +TypeID = 612 +Name = "a ramp" +Flags = {Clip,Unmove} + +TypeID = 613 +Name = "a ramp" +Flags = {Clip,Unmove} + +TypeID = 614 +Name = "sand" +Flags = {UseEvent,Bank,Unmove,Disguise} +Attributes = {Waypoints=160,DisguiseTarget=231} + +TypeID = 615 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=170,ExpireTarget=614,TotalExpireTime=30} + +TypeID = 616 +Name = "sand" +Flags = {Bank,Unmove,Disguise} +Attributes = {Waypoints=160,DisguiseTarget=231} + +TypeID = 617 +Name = "sand" +Flags = {Bank,Unmove,Expire,Disguise} +Attributes = {Waypoints=160,ExpireTarget=616,TotalExpireTime=4000,DisguiseTarget=231} + +TypeID = 618 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 619 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 620 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=618,TotalExpireTime=2200} + +TypeID = 621 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=620} + +TypeID = 622 +Name = "water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 623 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 624 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 625 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 626 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 627 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 628 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 629 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 630 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 631 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 632 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 633 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 634 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 635 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 636 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 637 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 638 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 639 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 640 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 641 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 642 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 643 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 644 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 645 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 646 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 647 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 648 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 649 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 650 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 651 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 652 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 653 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 654 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 655 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 656 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 657 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 658 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 659 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 660 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 661 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 662 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 663 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 664 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 665 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 666 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 667 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 668 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 669 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 670 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 671 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 672 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 673 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 674 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 675 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 676 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 677 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 678 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 679 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 680 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 681 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 682 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 683 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 684 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 685 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 686 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 687 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 688 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 689 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 690 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 691 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 692 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 693 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 694 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 695 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 696 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 697 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 698 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 699 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 700 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 701 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 702 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 703 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 704 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 705 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 706 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 707 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 708 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 709 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 710 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 711 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 712 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 713 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 714 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 715 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 716 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 717 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 718 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 719 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 720 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 721 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 722 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 723 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 724 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 725 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 726 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 727 +Name = "lava" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=193} + +TypeID = 728 +Name = "lava" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=193} + +TypeID = 729 +Name = "lava" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=193} + +TypeID = 730 +Name = "lava" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=193} + +TypeID = 731 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 732 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 733 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 734 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 735 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 736 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 737 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 738 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 739 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 740 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 741 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 742 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 743 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 744 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 745 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 746 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 747 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 748 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 749 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 750 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 751 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 752 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 753 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 754 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 755 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 756 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 757 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 758 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 759 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 760 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 761 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 762 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 763 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 764 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 765 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 766 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 767 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 768 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 769 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 770 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 771 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 772 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 773 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 774 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 775 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 776 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 777 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 778 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 779 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 780 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 781 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 782 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 783 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 784 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 785 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 786 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 787 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 788 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 789 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 790 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 791 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 792 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 793 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 794 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 795 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 796 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 797 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 798 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 799 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 800 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 801 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 802 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 803 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 804 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 805 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 806 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 807 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 808 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 809 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 810 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 811 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 812 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 813 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 814 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 815 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 816 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 817 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 818 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 819 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 820 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 821 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 822 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 823 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 824 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 825 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 826 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 827 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 828 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 829 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 830 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 831 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 832 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 833 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 834 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 835 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 836 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 837 +Name = "tar" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 838 +Name = "tar" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 839 +Name = "tar" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 840 +Name = "tar" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 841 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 842 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 843 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 844 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 845 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 846 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 847 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 848 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 849 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 850 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 851 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 852 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 853 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 854 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 855 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 856 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 857 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 858 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 859 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 860 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 861 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 862 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 863 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 864 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 865 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 866 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 867 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 868 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 869 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 870 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 871 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 872 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 873 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 874 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 875 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 876 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 877 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 878 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 879 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 880 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 881 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 882 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 883 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 884 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 885 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 886 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 887 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 888 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 889 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 890 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 891 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 892 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 893 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 894 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 895 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 896 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 897 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 898 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 899 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 900 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 901 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 902 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 903 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 904 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 905 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 906 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 907 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 908 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 909 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 910 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 911 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 912 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 913 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 914 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 915 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 916 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 917 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 918 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 919 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 920 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 921 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 922 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 923 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 924 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 925 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 926 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 927 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 928 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 929 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 930 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 931 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 932 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 933 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 934 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 935 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 936 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 937 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 938 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 939 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 940 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 941 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 942 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 943 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 944 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 945 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 946 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 947 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 948 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 949 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 950 +Name = "soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 951 +Name = "ploughed soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 952 +Name = "ploughed soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=180} + +TypeID = 953 +Name = "ploughed soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 954 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 955 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 956 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 957 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 958 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 959 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 960 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 961 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 962 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 963 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 964 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 965 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 966 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 967 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 968 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 969 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 970 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 971 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 972 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 973 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 974 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 975 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 976 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 977 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 978 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 979 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 980 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 981 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 982 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 983 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 984 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 985 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 986 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 987 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 988 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 989 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 990 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 991 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 992 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 993 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 994 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 995 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 996 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 997 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 998 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 999 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 1000 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 1001 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 1002 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 1003 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1004 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1005 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1006 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1007 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1008 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1009 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1010 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1011 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1012 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1013 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1014 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1015 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1016 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1017 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1018 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1019 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1020 +Name = "earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1021 +Name = "earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1022 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1023 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1024 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1025 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1026 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1027 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1028 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1029 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1030 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1031 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1032 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1033 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1034 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1035 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1036 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1037 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1038 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1039 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1040 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1041 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1042 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1043 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1044 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1045 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1046 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1047 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1048 +Name = "jungle grass " +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1049 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1050 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1051 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1052 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1053 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1054 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1055 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1056 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1057 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1058 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1059 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1060 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1061 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1062 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1063 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1064 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1065 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1066 +Name = "a pitfall" +Flags = {Bottom,CollisionEvent,Unmove} + +TypeID = 1067 +Name = "a pitfall" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {ExpireTarget=1066,TotalExpireTime=75} + +TypeID = 1068 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1069 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1070 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1071 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1072 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1073 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1074 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1075 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1076 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1077 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1078 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1079 +Name = "an ant-hill" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1080 +Name = "an earth hole" +Flags = {Bottom,CollisionEvent,Unmove,Avoid} + +TypeID = 1081 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1082 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1083 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1084 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1085 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1086 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1087 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1088 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1089 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1090 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1091 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1092 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1093 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1094 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1095 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1096 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1097 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1098 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1099 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay,Disguise} +Attributes = {Waypoints=0,DisguiseTarget=1128} + +TypeID = 1100 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1101 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1102 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1103 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1104 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1105 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1106 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1107 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1108 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1109 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1110 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1111 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1112 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1113 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1114 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1115 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1116 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1117 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1118 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1119 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1120 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1121 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1122 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1123 +Name = "a mountain" +Flags = {Top,Unmove} + +TypeID = 1124 +Name = "a mountain" +Flags = {Top,Unmove} + +TypeID = 1125 +Name = "a mountain" +Flags = {Top,Unmove} + +TypeID = 1126 +Name = "a mountain" +Flags = {Top,Unmove} + +TypeID = 1127 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 1128 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 1129 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1130 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1131 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1132 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1133 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1134 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1135 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1136 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1137 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1138 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1139 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1140 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1141 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1142 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1143 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1144 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1145 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1146 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1147 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1148 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1149 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1150 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1151 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1152 +Name = "a flat roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1153 +Name = "a flat roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1154 +Name = "a flat roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1155 +Name = "a flat roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1156 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=160} + +TypeID = 1157 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1158 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1159 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1160 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1161 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1162 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1163 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1164 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1165 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1166 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1167 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1168 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1169 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1170 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1171 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1172 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1173 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1174 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1175 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1176 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1177 +Name = "a wooden roof" +Flags = {Unpass,Unmove} + +TypeID = 1178 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1179 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1180 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1181 +Name = "a wooden roof" +Flags = {Unpass,Unmove} + +TypeID = 1182 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1183 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1184 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1185 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1186 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1187 +Name = "a wooden roof" +Flags = {Unpass,Unmove} + +TypeID = 1188 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1189 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1190 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1191 +Name = "a wooden roof" +Flags = {Unpass,Unmove} + +TypeID = 1192 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1193 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1194 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1195 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1196 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1197 +Name = "a dried grass roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1198 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1199 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1200 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1201 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1202 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1203 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1204 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1205 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1206 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1207 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1208 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1209 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1210 +Name = "a chess board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1211 +Name = "a chess board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1212 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1213 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1214 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1215 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1216 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1217 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1218 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1219 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1220 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1221 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1222 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1223 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1224 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1225 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1226 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1227 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1228 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1229 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1230 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1231 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1232 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1233 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1234 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1235 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1236 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1237 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1238 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1239 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1240 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1241 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1242 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1243 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1244 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1245 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1246 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1247 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1248 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1249 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1250 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1251 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1252 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1253 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1254 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1255 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1256 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1257 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1258 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1259 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1260 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1261 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1262 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1263 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1264 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1265 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1266 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1267 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1268 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1269 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1270 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1271 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1272 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1273 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1274 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1275 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1276 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1277 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1278 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1279 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1280 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1281 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1282 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1283 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1284 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1285 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1286 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1287 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1288 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1289 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1290 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1291 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1292 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1293 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1294 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1295 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1296 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1297 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1298 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1299 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1300 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1301 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1302 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1303 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1304 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1305 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1306 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1307 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1308 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1309 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1310 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1311 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1312 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1313 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1314 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1315 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1316 +Name = "sandstone" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 1317 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1318 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1319 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1320 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1321 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1322 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1323 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1324 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1325 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1326 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1327 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1328 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1329 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1330 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1331 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1332 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1333 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1334 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1335 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1336 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1337 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1338 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1339 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1340 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1341 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1342 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1343 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1344 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1345 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1346 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1347 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1348 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1349 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1350 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1351 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1352 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1353 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1354 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1355 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1356 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1357 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1358 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1359 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1360 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1361 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1362 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1363 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1364 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1365 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1366 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1367 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1368 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1369 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1370 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1371 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1372 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1373 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1374 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1375 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1376 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1377 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1378 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1379 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1380 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1381 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1382 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1383 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1384 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1385 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1386 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1387 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1388 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1389 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1390 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1391 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1392 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1393 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1394 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1395 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1396 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1397 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1398 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1399 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1400 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1401 +Name = "a wall fountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1402 +Name = "a wall fountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1403 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1404 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1405 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1406 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1407 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1408 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1409 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1410 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1411 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1412 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1413 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1414 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1415 +Name = "a paravent" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1416 +Name = "a paravent wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1417 +Name = "a paravent wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1418 +Name = "a paravent wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1419 +Name = "a paravent wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1420 +Name = "a paravent wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1421 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1422 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1423 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1424 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1425 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1426 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1427 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1428 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1429 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1430 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1431 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1432 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1433 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1434 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1435 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1436 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1437 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1438 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1439 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1440 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1441 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1442 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1443 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1444 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1445 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1446 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1447 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1448 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1449 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1450 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1451 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1452 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1453 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1454 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1455 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1456 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1457 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1458 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1459 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1460 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1461 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1462 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1463 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1464 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1465 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1466 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1467 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1468 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1469 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1470 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1471 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1472 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1473 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1474 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1475 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1476 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1477 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1478 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1479 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1480 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1481 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1482 +Name = "a stone wall" +Flags = {Top,Unmove} + +TypeID = 1483 +Name = "a stone wall" +Flags = {Top,Unmove} + +TypeID = 1484 +Name = "a stone wall" +Flags = {Top,Unmove} + +TypeID = 1485 +Name = "a stone wall" +Flags = {Top,Unmove} + +TypeID = 1486 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1487 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1488 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1489 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1490 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1491 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1492 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1493 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1494 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1495 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1496 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1497 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1498 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1499 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1500 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1501 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1502 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1503 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1504 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1505 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1506 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1507 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1508 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1509 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1510 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1511 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1512 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1513 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1514 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1515 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1516 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1517 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1518 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1519 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1520 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1521 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1522 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1523 +Name = "a bamboo palisade" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1524 +Name = "a bamboo palisade" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1525 +Name = "a bamboo palisade" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1526 +Name = "a bamboo fence" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1527 +Name = "a bamboo fence" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1528 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1529 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1530 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1531 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1532 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1533 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1534 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1535 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1536 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1537 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1538 +Name = "a bamboo wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1539 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1540 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1541 +Name = "a bamboo wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1542 +Name = "a bamboo palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1543 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1544 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1545 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1546 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1547 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1548 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1549 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1550 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1551 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1552 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1553 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1554 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1555 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1556 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1557 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1558 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1559 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1560 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1561 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1562 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1563 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1564 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1565 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1566 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1567 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1568 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1569 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1570 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1571 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1572 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1573 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1574 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1575 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1576 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1577 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1578 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1579 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1580 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1581 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1582 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1583 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1584 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1585 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1586 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1587 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1588 +Name = "a grass wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1589 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1590 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1591 +Name = "a grass wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1592 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1593 +Name = "a grass archway" +Flags = {Top,Unmove} + +TypeID = 1594 +Name = "a grass archway" +Flags = {Top,Unmove} + +TypeID = 1595 +Name = "a liane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1596 +Name = "a liane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1597 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1598 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1599 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1600 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1601 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1602 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1603 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1604 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1605 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1606 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1607 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1608 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1609 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1610 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1611 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1612 +Name = "a palisade" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1613 +Name = "a palisade" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1614 +Name = "a palisade" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1615 +Name = "a palisade" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1616 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1617 +Name = "a palisade" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1618 +Name = "a temple wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1619 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1620 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1621 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1622 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1623 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1624 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1625 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1626 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1627 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1628 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1629 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1630 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1631 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1632 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1633 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1634 +Name = "a buttress" +Flags = {Top,Unmove} + +TypeID = 1635 +Name = "a buttress" +Flags = {Top,Unmove} + +TypeID = 1636 +Name = "a buttress" +Flags = {Top,Unmove} + +TypeID = 1637 +Name = "a buttress" +Flags = {Top,Unmove} + +TypeID = 1638 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1639 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1640 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1641 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1642 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1643 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1644 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1645 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1646 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1647 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1648 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1649 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1650 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1651 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1652 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1653 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1654 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1655 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1656 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1657 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1658 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1659 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1660 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1661 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1662 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1663 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1664 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1665 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1666 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1667 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1668 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1669 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1670 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1671 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1672 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1673 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1674 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1675 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1676 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1677 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1678 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1679 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1680 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1681 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1682 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1683 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1684 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1685 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1686 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1687 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1688 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1689 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1690 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1691 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1692 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1693 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1694 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1695 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1696 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1697 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1698 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1699 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1700 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1701 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1702 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1703 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1704 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1705 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1706 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1707 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1708 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1709 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1710 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1711 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1712 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1713 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1714 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1715 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1716 +Name = "a waterfall" +Flags = {Unmove} + +TypeID = 1717 +Name = "a waterfall" +Flags = {Unmove} + +TypeID = 1718 +Name = "a waterfall" +Flags = {Clip,CollisionEvent,Unmove} + +TypeID = 1719 +Name = "a waterfall" +Flags = {Clip,CollisionEvent,Unmove} + +TypeID = 1720 +Name = "a waterfall" +Flags = {Clip,CollisionEvent,Unmove} + +TypeID = 1721 +Name = "a waterfall" +Flags = {Clip,CollisionEvent,Unmove} + +TypeID = 1722 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1723 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1724 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1725 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1726 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1727 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1728 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1729 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1730 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1731 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1732 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1733 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1734 +Name = "a framework window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1735 +Name = "a framework window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1736 +Name = "a brick window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1737 +Name = "a brick window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1738 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1739 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1740 +Name = "an oriental window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1741 +Name = "an oriental window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1742 +Name = "an oriental window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1743 +Name = "an oriental window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1744 +Name = "a sandstone window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1745 +Name = "a sandstone window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1746 +Name = "a sandstone window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1747 +Name = "a sandstone window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1748 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1749 +Name = "a sail" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1750 +Name = "a sail" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1751 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1752 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1753 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1754 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1755 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1756 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1757 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1758 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1759 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1760 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1761 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1762 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1763 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1764 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1765 +Name = "a small sail" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1766 +Name = "a small sail" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1767 +Name = "a paddle" +Flags = {Unmove} + +TypeID = 1768 +Name = "a paddle" +Flags = {Unmove} + +TypeID = 1769 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1770 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1771 +Name = "a drawbridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=90} + +TypeID = 1772 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1773 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1774 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1775 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1776 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1777 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1778 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1779 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1780 +Name = "a stone" +Flags = {Take} +Attributes = {Weight=41000} + +TypeID = 1781 +Name = "a small stone" +Flags = {Cumulative,Take,Distance} +Attributes = {Weight=360,Range=7,Attack=10,Defense=0,MissileEffect=10,Fragility=7} + +TypeID = 1782 +Name = "a stone" +Flags = {Take} +Attributes = {Weight=78000} + +TypeID = 1783 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1784 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1785 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1786 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1787 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1788 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1789 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1790 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1791 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1792 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1793 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1794 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1795 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1796 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1797 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1798 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1799 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1800 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1801 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1802 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1803 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1804 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1805 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1806 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1807 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1808 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1809 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1810 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1811 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1812 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1813 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1814 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1815 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1816 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1817 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1818 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1819 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1820 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1821 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1822 +Name = "a stone pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1823 +Name = "a stone pile" +Flags = {Unmove} + +TypeID = 1824 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1825 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1826 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1827 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1828 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1829 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1830 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1831 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1832 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1833 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1834 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1835 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1836 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1837 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1838 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1839 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1840 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1841 +Name = "a blue shrine stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1842 +Name = "a red shrine stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1843 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1844 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1845 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1846 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1847 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1848 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1849 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1850 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1851 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1852 +Name = "stones" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1853 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1854 +Name = "stones" +Flags = {Unmove} + +TypeID = 1855 +Name = "stones" +Flags = {Unmove} + +TypeID = 1856 +Name = "a stone" +Flags = {Unmove} + +TypeID = 1857 +Name = "stones" +Flags = {Unmove} + +TypeID = 1858 +Name = "stones" +Flags = {Unmove} + +TypeID = 1859 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1860 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1861 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1862 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1863 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1864 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1865 +Name = "a stone" +Flags = {Unmove} + +TypeID = 1866 +Name = "a stone" +Flags = {Unmove} + +TypeID = 1867 +Name = "a stone" +Flags = {Unmove} + +TypeID = 1868 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1869 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1870 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1871 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1872 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1873 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1874 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1875 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1876 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1877 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1878 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1879 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1880 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1881 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1882 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1883 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1884 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1885 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1886 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1887 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1888 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1889 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1890 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1891 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1892 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1893 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1894 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1895 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1896 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1897 +Name = "debris" +Flags = {Unmove} + +TypeID = 1898 +Name = "debris" +Flags = {Unmove} + +TypeID = 1899 +Name = "debris" +Flags = {Unmove} + +TypeID = 1900 +Name = "debris" +Flags = {Unmove} + +TypeID = 1901 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1902 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1903 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1904 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1905 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1906 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1907 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1908 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1909 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1910 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1911 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1912 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1913 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1914 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1915 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1916 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1917 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1918 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1919 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1920 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1921 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1922 +Name = "a fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1923 +Name = "a fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1924 +Name = "a fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1925 +Name = "a fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1926 +Name = "a water basin" +Flags = {Bottom,UseEvent,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1927 +Name = "a water basin" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1928 +Name = "a water basin" +Flags = {Bottom,UseEvent,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1929 +Name = "a water basin" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1930 +Name = "a draw well" +Flags = {Bottom,UseEvent,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1931 +Name = "a draw well" +Flags = {Bottom,UseEvent,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1932 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1933 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1934 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1935 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1936 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1937 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1938 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1939 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1940 +Name = "a small basin" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1941 +Name = "a water wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1942 +Name = "a water wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1943 +Name = "a millstone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1944 +Name = "a millstone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1945 +Name = "a millstone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1946 +Name = "a millstone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1947 +Name = "stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1948 +Name = "a ladder" +Flags = {Bottom,UseEvent,ForceUse,Unmove} + +TypeID = 1949 +Name = "a magic forcefield" +Description = "You can see the other side through it" +Flags = {Bottom,CollisionEvent,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 1950 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1951 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1952 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1953 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1954 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1955 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1956 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1957 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1958 +Name = "wooden stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1959 +Name = "a mystic flame" +Description = "You feel drawn to the mesmerizing light" +Flags = {Bottom,CollisionEvent,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=173} + +TypeID = 1960 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1961 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1962 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1963 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1964 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1965 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1966 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1967 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1968 +Name = "a ladder" +Flags = {Bottom,UseEvent,ForceUse,Unmove} + +TypeID = 1969 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1970 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1971 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1972 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1973 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1974 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1975 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1976 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1977 +Name = "stone stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1978 +Name = "stone stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1979 +Name = "a grave" +Flags = {Bottom,Unmove,AllowDistRead} + +TypeID = 1980 +Name = "a grave stone" +Flags = {Bottom,Unpass,Unmove,AllowDistRead} + +TypeID = 1981 +Name = "a grave stone" +Flags = {Bottom,Unpass,Unmove,AllowDistRead} + +TypeID = 1982 +Name = "a grave stone" +Flags = {Unmove,AllowDistRead} + +TypeID = 1983 +Name = "a stone coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1984 +Name = "a stone coffin" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 1985 +Name = "a stone coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1986 +Name = "a stone coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1987 +Name = "a stone coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1988 +Name = "a buried coffin" +Flags = {Unmove} + +TypeID = 1989 +Name = "a buried coffin" +Flags = {Unmove} + +TypeID = 1990 +Name = "a wooden coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height,Disguise} +Attributes = {Capacity=6,DisguiseTarget=2474} + +TypeID = 1991 +Name = "a wooden coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height,Disguise} +Attributes = {Capacity=6,DisguiseTarget=2476} + +TypeID = 1992 +Name = "a sarcophagus" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1993 +Name = "a sarcophagus" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 1994 +Name = "a sarcophagus" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1995 +Name = "a sarcophagus" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 1996 +Name = "a stone circle" +Flags = {Unmove} + +TypeID = 1997 +Name = "an unlit campfire" +Flags = {Unmove} + +TypeID = 1998 +Name = "a campfire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,NoReplace} +Attributes = {Brightness=7,LightColor=206,AvoidDamageTypes=FIRE} +MagicField = {Type=FIRE,Count=70,Damage=20} + +TypeID = 1999 +Name = "a campfire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,NoReplace} +Attributes = {Brightness=5,LightColor=206,AvoidDamageTypes=FIRE} +MagicField = {Type=FIRE,Count=50,Damage=10} + +TypeID = 2000 +Name = "a campfire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,NoReplace} +Attributes = {Brightness=3,LightColor=206,AvoidDamageTypes=FIRE} +MagicField = {Type=FIRE,Count=50,Damage=10} + +TypeID = 2001 +Name = "an unlit campfire" +Flags = {Unmove} + +TypeID = 2002 +Name = "a campfire" +Flags = {Unpass,Unmove} + +TypeID = 2003 +Name = "a campfire" +Flags = {Unpass,Unmove} +Attributes = {Brightness=5,LightColor=206} + +TypeID = 2004 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2005 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2006 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2007 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2008 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2009 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2010 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=3,LightColor=199} + +TypeID = 2011 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=3,LightColor=199} + +TypeID = 2012 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2013 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2014 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2015 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2016 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2017 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2018 +Name = "a dragon flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2019 +Name = "a castle flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2020 +Name = "a flag of Tibia" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2021 +Name = "a street sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2022 +Name = "a street sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2023 +Name = "a sign" +Flags = {Unmove,Hang,AllowDistRead} + +TypeID = 2024 +Name = "a sign" +Flags = {Unmove,Hang,AllowDistRead} + +TypeID = 2025 +Name = "a statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2059,DestroyTarget=3141} + +TypeID = 2026 +Name = "a statue" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2027 +Name = "a hero statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2028 +Name = "a monument" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2029 +Name = "a minotaur statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2045,DestroyTarget=3142} + +TypeID = 2030 +Name = "a goblin statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2048,DestroyTarget=3142} + +TypeID = 2031 +Name = "an angel statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2032 +Name = "a dwarven statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2033 +Name = "a watchdog statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2034 +Name = "a sandstone statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2035 +Name = "a gargoyle statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2036 +Name = "a gargoyle statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2037 +Name = "a gargoyle statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2038 +Name = "a gargoyle statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2039 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2040 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2041 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2042 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2043 +Name = "a minotaur statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2044,DestroyTarget=3142} + +TypeID = 2044 +Name = "a minotaur statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2029,DestroyTarget=3142} + +TypeID = 2045 +Name = "a minotaur statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2043,DestroyTarget=3142} + +TypeID = 2046 +Name = "a goblin statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2047,DestroyTarget=3142} + +TypeID = 2047 +Name = "a goblin statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2030,DestroyTarget=3142} + +TypeID = 2048 +Name = "a goblin statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2046,DestroyTarget=3142} + +TypeID = 2049 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2050 +Name = "a cobra statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2051 +Name = "a cobra statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2052 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2053 +Name = "a cobra statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2054 +Name = "a cobra statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2055 +Name = "a cobra statue" +Flags = {Unmove,Hang} + +TypeID = 2056 +Name = "an ornament" +Flags = {Unmove,Hang} + +TypeID = 2057 +Name = "a cobra statue" +Flags = {Unmove,Hang} + +TypeID = 2058 +Name = "an ornament" +Flags = {Unmove,Hang} + +TypeID = 2059 +Name = "a statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2060,DestroyTarget=3141} + +TypeID = 2060 +Name = "a statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2061,DestroyTarget=3141} + +TypeID = 2061 +Name = "a statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2025,DestroyTarget=3141} + +TypeID = 2062 +Name = "a sacral statue" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2063} + +TypeID = 2063 +Name = "a sacral statue" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2062,Brightness=6,LightColor=206} + +TypeID = 2064 +Name = "a sacral statue" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2065} + +TypeID = 2065 +Name = "a sacral statue" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2064,Brightness=6,LightColor=206} + +TypeID = 2066 +Name = "a broken lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2067 +Name = "a broken lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2068 +Name = "a broken lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2069 +Name = "a broken lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2070 +Name = "a lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2071 +Name = "a lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2072 +Name = "a lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2073 +Name = "a lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2074 +Name = "a small pagoda" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2075 +Name = "a small lit pagoda" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=6,LightColor=207} + +TypeID = 2076 +Name = "a stone snake wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2077 +Name = "a snake wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2078 +Name = "a stone snake wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2079 +Name = "a snake wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2080 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2081 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2082 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2083 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2084 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2085 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2086 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2087 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2088 +Name = "a giant lizard claw" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2089 +Name = "a giant lizard claw" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2090 +Name = "a giant lizard claw" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2091 +Name = "a giant lizard claw" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2092 +Name = "a stone snake wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2093 +Name = "a stone snake pagoda" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2094 +Name = "a dried well" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2095 +Name = "a dried well" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2096 +Name = "a dried well" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2097 +Name = "a dried well" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2098 +Name = "a poison well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=SLIME,Brightness=2,LightColor=104} + +TypeID = 2099 +Name = "a poison well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=SLIME,Brightness=2,LightColor=104} + +TypeID = 2100 +Name = "a poison well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=SLIME,Brightness=2,LightColor=104} + +TypeID = 2101 +Name = "a poison well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=SLIME,Brightness=2,LightColor=104} + +TypeID = 2102 +Name = "a pagoda" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2103 +Name = "a pagoda" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2104 +Name = "a huntress statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2105 +Name = "a huntress statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2106 +Name = "a huntress statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2107 +Name = "a huntress statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2108 +Name = "a street lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2109,Brightness=0,LightColor=215} + +TypeID = 2109 +Name = "a street lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2108,Brightness=7,LightColor=207} + +TypeID = 2110 +Name = "a coal basin" +Flags = {Unpass,Unmove,Height} +Attributes = {Brightness=8,LightColor=207} + +TypeID = 2111 +Name = "a coal basin" +Flags = {Unpass,Unmove,Height} +Attributes = {Brightness=8,LightColor=206} + +TypeID = 2112 +Name = "a coal basin" +Flags = {Unpass,Unmove,Height} +Attributes = {Brightness=8,LightColor=206} + +TypeID = 2113 +Name = "a coal basin" +Flags = {Unpass,Unmove,Height} +Attributes = {Brightness=8,LightColor=206} + +TypeID = 2114 +Name = "an empty coal basin" +Flags = {CollisionEvent,Unpass,Unmove,Height} +Attributes = {Brightness=0,LightColor=215} + +TypeID = 2115 +Name = "a stone coal basin" +Flags = {Unpass,Unmove,Unlay,Height} +Attributes = {Brightness=7,LightColor=206} + +TypeID = 2116 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2117,Brightness=0,LightColor=215} + +TypeID = 2117 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=207} + +TypeID = 2118 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=FIRE,Brightness=7,LightColor=200,ExpireTarget=2119,TotalExpireTime=200} +MagicField = {Type=FIRE,Count=70,Damage=20} + +TypeID = 2119 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=FIRE,Brightness=5,LightColor=206,ExpireTarget=2120,TotalExpireTime=150} +MagicField = {Type=FIRE,Count=50,Damage=10} + +TypeID = 2120 +Name = "a fire" +Flags = {Unmove,MagicField,Expire} +Attributes = {Brightness=3,LightColor=206,ExpireTarget=0,TotalExpireTime=100} + +TypeID = 2121 +Name = "poison gas" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=POISON,Brightness=2,LightColor=104,ExpireTarget=0,TotalExpireTime=250} +MagicField = {Type=POISON,Count=100,Damage=5} + +TypeID = 2122 +Name = "an energy field" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=ENERGY,Brightness=4,LightColor=137,ExpireTarget=0,TotalExpireTime=100} +MagicField = {Type=ENERGY,Count=25,Damage=30} + +TypeID = 2123 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField} +Attributes = {AvoidDamageTypes=FIRE,Brightness=7,LightColor=200} +MagicField = {Type=FIRE,Count=70,Damage=20} + +TypeID = 2124 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField} +Attributes = {AvoidDamageTypes=FIRE,Brightness=5,LightColor=206} +MagicField = {Type=FIRE,Count=50,Damage=10} + +TypeID = 2125 +Name = "a fire" +Flags = {Unmove,MagicField} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 2126 +Name = "an energy field" +Flags = {CollisionEvent,Unmove,Avoid,MagicField} +Attributes = {AvoidDamageTypes=ENERGY,Brightness=4,LightColor=137} +MagicField = {Type=ENERGY,Count=25,Damage=30} + +TypeID = 2127 +Name = "poison gas" +Flags = {CollisionEvent,Unmove,Avoid,MagicField} +Attributes = {AvoidDamageTypes=POISON,Brightness=2,LightColor=104} +MagicField = {Type=POISON,Count=100,Damage=5} + +TypeID = 2128 +Name = "a magic wall" +Flags = {Unpass,CollisionEvent,Unmove,Unthrow,Unlay,MagicField,Expire} +Attributes = {Brightness=3,LightColor=5,ExpireTarget=0,TotalExpireTime=20} + +TypeID = 2129 +Name = "a magic wall" +Flags = {Unpass,Unmove,Unthrow,Unlay,MagicField} +Attributes = {Brightness=3,LightColor=5} + +TypeID = 2130 +Name = "rush wood" +Flags = {Unpass,Unmove,Unlay,MagicField,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=45} + +TypeID = 2131 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=FIRE,Brightness=7,LightColor=206,ExpireTarget=2132,TotalExpireTime=5} +MagicField = {Type=FIRE,Count=70,Damage=20} + +TypeID = 2132 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=FIRE,Brightness=5,LightColor=206,ExpireTarget=2133,TotalExpireTime=5} +MagicField = {Type=FIRE,Count=50,Damage=10} + +TypeID = 2133 +Name = "a fire" +Flags = {Unmove,MagicField,Expire} +Attributes = {Brightness=3,LightColor=207,ExpireTarget=0,TotalExpireTime=5} + +TypeID = 2134 +Name = "poison gas" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=POISON,Brightness=2,LightColor=214,ExpireTarget=0,TotalExpireTime=8} +MagicField = {Type=POISON,Count=100,Damage=5} + +TypeID = 2135 +Name = "an energy field" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=ENERGY,Brightness=4,LightColor=214,ExpireTarget=0,TotalExpireTime=5} +MagicField = {Type=ENERGY,Count=25,Damage=30} + +TypeID = 2136 +Name = "smoke" +Flags = {Unmove,MagicField} + +TypeID = 2137 +Name = "a searing fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,MagicField,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=7,LightColor=203,ExpireTarget=2138,TotalExpireTime=7} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2138 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,MagicField,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=5,LightColor=203,ExpireTarget=2151,TotalExpireTime=2} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2139 +Name = "a fire" +Flags = {Unmove,Avoid,Expire,Disguise,MagicField,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,ExpireTarget=2140,TotalExpireTime=2,DisguiseTarget=2140} + +TypeID = 2140 +Name = "ashes" +Flags = {Unmove,Avoid,Expire} +Attributes = {AvoidDamageTypes=PHYSICAL,ExpireTarget=2137,TotalExpireTime=8} + +TypeID = 2141 +Name = "a searing fire" +Flags = {CollisionEvent,Unmove,Avoid,Disguise,MagicField,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=7,LightColor=203,DisguiseTarget=2137} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2142 +Name = "a searing fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,Disguise,MagicField,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=7,LightColor=203,ExpireTarget=2140,TotalExpireTime=7,DisguiseTarget=2137} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2143 +Name = "ashes" +Flags = {CollisionEvent,Unmove,Avoid,Expire,Disguise,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,ExpireTarget=2139,TotalExpireTime=1,DisguiseTarget=2140} + +TypeID = 2144 +Name = "lava" +Flags = {Bank,Unmove,Avoid} +Attributes = {Waypoints=150,Brightness=4,LightColor=193} + +TypeID = 2145 +Name = "strange slits" +Flags = {CollisionEvent,Unmove} + +TypeID = 2146 +Name = "blades" +Flags = {CollisionEvent,Unmove,Expire} +Attributes = {ExpireTarget=2145,TotalExpireTime=3} + +TypeID = 2147 +Name = "strange holes" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2148,TotalExpireTime=1} + +TypeID = 2148 +Name = "spikes" +Flags = {CollisionEvent,Unmove,Expire} +Attributes = {ExpireTarget=2147,TotalExpireTime=3} + +TypeID = 2149 +Name = "a searing fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,Disguise,MagicField} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=7,LightColor=209,ExpireTarget=2150,TotalExpireTime=5,DisguiseTarget=2137} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2150 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,Disguise,MagicField} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=5,LightColor=209,ExpireTarget=2149,TotalExpireTime=1,DisguiseTarget=2138} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2151 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,Disguise,MagicField} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=2,LightColor=209,ExpireTarget=2139,TotalExpireTime=2,DisguiseTarget=2138} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2152 +Name = "a stone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2153 +Name = "a marble pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2154 +Name = "a wooden railing" +Flags = {Top,Unpass,Unmove,Unlay} + +TypeID = 2155 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2156 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2157 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2158 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2159 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2160 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2161 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2162 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2163 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2164 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2165 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2166 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2167 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2168 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2169 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2170 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2171 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2172 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2173 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2174 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2175 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2176 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2177 +Name = "a closed fence gate" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2178} + +TypeID = 2178 +Name = "an open fence gate" +Flags = {Top,ChangeUse,Unmove} +Attributes = {ChangeTarget=2177} + +TypeID = 2179 +Name = "a closed fence gate" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2180} + +TypeID = 2180 +Name = "an open fence gate" +Flags = {Top,ChangeUse,Unmove} +Attributes = {ChangeTarget=2179} + +TypeID = 2181 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2182 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2183 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2184 +Name = "bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2185 +Name = "bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2186 +Name = "nothing special" +Flags = {Bottom,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=2186} + +TypeID = 2187 +Name = "nothing special" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2188 +Name = "a sandstone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2189 +Name = "a sandstone statue" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2190 +Name = "an oriental pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2191 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 2192 +Name = "a ramp" +Flags = {CollisionEvent,Bottom,Unmove,Avoid,Height} + +TypeID = 2193 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 2194 +Name = "a ramp" +Flags = {CollisionEvent,Bottom,Unmove,Avoid,Height} + +TypeID = 2195 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 2196 +Name = "a ramp" +Flags = {CollisionEvent,Bottom,Unmove,Avoid,Height} + +TypeID = 2197 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 2198 +Name = "a ramp" +Flags = {CollisionEvent,Bottom,Unmove,Avoid,Height} + +TypeID = 2199 +Name = "an obelisk" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2200 +Name = "a broken obelisk" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2201 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2202 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2203 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2204 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2205 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2206 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2207 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2208 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2209 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2210 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2211 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2212 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2213 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2214 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2215 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2216 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2217 +Name = "an ominous pillar" +Flags = {Bottom,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=2190} + +TypeID = 2218 +Name = "a ramp" +Flags = {Bottom,Unpass,Unmove,Unlay,Height} + +TypeID = 2219 +Name = "a ramp" +Flags = {Bottom,Unpass,Unmove,Unlay,Height} + +TypeID = 2220 +Name = "a ramp" +Flags = {Bottom,Unpass,Unmove,Unlay,Height} + +TypeID = 2221 +Name = "a ramp" +Flags = {Bottom,Unpass,Unmove,Unlay,Height} + +TypeID = 2222 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2223 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2224 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2225 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2226 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2227 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2228 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2229 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2230 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2231 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2232 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2233 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2234 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2235 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2236 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2237 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2238 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2239 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2240 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2241 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2242 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2243 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2244 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2245 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2246 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2247 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2248 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2249 +Name = "a bamboo pole" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2250 +Name = "a bamboo pole" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2251 +Name = "a bamboo pole" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2252 +Name = "a bamboo pole" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2253 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2254 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2255 +Name = "a short pillar" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2256 +Name = "a short pillar" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2257 +Name = "a short pillar" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2258 +Name = "a short pillar" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2259 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2260 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2261 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2262 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2263 +Name = "a stone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2264 +Name = "a broken stone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2265 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2266 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2267 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2268 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2269 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2270 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2271 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2272 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2273 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2274 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2275 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2276 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2277 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2278 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2279 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2280 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2281 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2282 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2283 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2284 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2285 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2286 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2287 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2288 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2289 +Name = "a stone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2290 +Name = "a broken stone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2291 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2292 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2293 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2294 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2295 +Name = "wooden bars" +Description = "They already have some cracks and look rather fragile" +Flags = {Unpass,Unmove,Unlay,Destroy} +Attributes = {DestroyTarget=3146} + +TypeID = 2296 +Name = "wooden bars" +Description = "They already have some cracks and look rather fragile" +Flags = {Unpass,Unmove,Unlay,Destroy} +Attributes = {DestroyTarget=3145} + +TypeID = 2297 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2298 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2299 +Name = "a small totem pole" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2300 +Name = "a large totem pole" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2301 +Name = "a totem pole" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2302 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2303 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2304 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2305 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2306 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2307 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2308 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2309 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2310 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2311 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2312 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2313 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2314 +Name = "a big table" +Flags = {Destroy,Height,Avoid} +Attributes = {DestroyTarget=3138} + +TypeID = 2315 +Name = "a square table" +Flags = {Destroy,Height,Avoid} +Attributes = {DestroyTarget=3138} + +TypeID = 2316 +Name = "a small round table" +Flags = {Destroy,Height,Avoid} +Attributes = {DestroyTarget=3138} + +TypeID = 2317 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2318 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2319 +Name = "a small table" +Flags = {Destroy,Height,Avoid} +Attributes = {DestroyTarget=3140} + +TypeID = 2320 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2321 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2322 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2323 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2324 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2325 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2326 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2327 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2328 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2329 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2330 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2331 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2332 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2333 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2334 +Name = "a passthrough" +Flags = {Bottom,Door,Unpass,Unmove,Height} + +TypeID = 2335 +Name = "an open passthrough" +Flags = {Bottom,Door,Unmove} + +TypeID = 2336 +Name = "a passthrough" +Flags = {Bottom,Door,Unpass,Unmove,Height} + +TypeID = 2337 +Name = "an open passthrough" +Flags = {Bottom,Door,Unmove} + +TypeID = 2338 +Name = "a passthrough" +Flags = {Door,Unpass,Unmove,Height} + +TypeID = 2339 +Name = "an open passthrough" +Flags = {Door,Unmove} + +TypeID = 2340 +Name = "a passthrough" +Flags = {Door,Unpass,Unmove,Height} + +TypeID = 2341 +Name = "an open passthrough" +Flags = {Door,Unmove} + +TypeID = 2342 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2343 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2344 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2345 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2346 +Name = "a carved stone table" +Flags = {Rotate,Destroy,Height} +Attributes = {RotateTarget=2347,DestroyTarget=3141} + +TypeID = 2347 +Name = "a carved stone table" +Flags = {Rotate,Destroy,Height} +Attributes = {RotateTarget=2346,DestroyTarget=3141} + +TypeID = 2348 +Name = "a tusk table" +Flags = {Rotate,Destroy,Height} +Attributes = {RotateTarget=2349,DestroyTarget=3137} + +TypeID = 2349 +Name = "a tusk table" +Flags = {Rotate,Destroy,Height} +Attributes = {RotateTarget=2348,DestroyTarget=3137} + +TypeID = 2350 +Name = "a bamboo table" +Flags = {Rotate,Destroy,Height} +Attributes = {RotateTarget=2351,DestroyTarget=3137} + +TypeID = 2351 +Name = "a bamboo table" +Flags = {Rotate,Destroy,Height} +Attributes = {RotateTarget=2350,DestroyTarget=3137} + +TypeID = 2352 +Name = "a thick trunk" +Flags = {Destroy,Height} +Attributes = {DestroyTarget=3136} + +TypeID = 2353 +Name = "an ornamented stone table" +Flags = {Rotate,Destroy,Height} +Attributes = {RotateTarget=2346,DestroyTarget=3141} + +TypeID = 2354 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2355 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2356 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2357 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2358 +Name = "a wooden chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2359,DestroyTarget=3138} + +TypeID = 2359 +Name = "a wooden chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2360,DestroyTarget=3138} + +TypeID = 2360 +Name = "a wooden chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2361,DestroyTarget=3138} + +TypeID = 2361 +Name = "a wooden chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2358,DestroyTarget=3138} + +TypeID = 2362 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2363 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2364 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2365 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2366 +Name = "a sofa chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2369,DestroyTarget=3139} + +TypeID = 2367 +Name = "a sofa chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2368,DestroyTarget=3139} + +TypeID = 2368 +Name = "a sofa chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2366,DestroyTarget=3139} + +TypeID = 2369 +Name = "a sofa chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2367,DestroyTarget=3139} + +TypeID = 2370 +Name = "a bench" +Flags = {Unmove,Avoid,Height} + +TypeID = 2371 +Name = "a bench" +Flags = {Unmove,Avoid,Height} + +TypeID = 2372 +Name = "a bench" +Flags = {Unmove,Avoid,Height} + +TypeID = 2373 +Name = "a bench" +Flags = {Unmove,Avoid,Height} + +TypeID = 2374 +Name = "a red cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2377,DestroyTarget=3138} + +TypeID = 2375 +Name = "a red cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2376,DestroyTarget=3138} + +TypeID = 2376 +Name = "a red cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2374,DestroyTarget=3138} + +TypeID = 2377 +Name = "a red cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2375,DestroyTarget=3138} + +TypeID = 2378 +Name = "a green cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2381,DestroyTarget=3138} + +TypeID = 2379 +Name = "a green cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2380,DestroyTarget=3138} + +TypeID = 2380 +Name = "a green cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2378,DestroyTarget=3138} + +TypeID = 2381 +Name = "a green cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2379,DestroyTarget=3138} + +TypeID = 2382 +Name = "a rocking chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2385,DestroyTarget=3138} + +TypeID = 2383 +Name = "a rocking chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2384,DestroyTarget=3138} + +TypeID = 2384 +Name = "a rocking chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2382,DestroyTarget=3138} + +TypeID = 2385 +Name = "a rocking chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2383,DestroyTarget=3138} + +TypeID = 2386 +Name = "a small purple pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2387 +Name = "a small green pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2388 +Name = "a small red pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2389 +Name = "a small blue pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2390 +Name = "a small orange pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2391 +Name = "a small turquoise pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2392 +Name = "a small white pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2393 +Name = "a heart pillow" +Flags = {Take} +Attributes = {Weight=1700} + +TypeID = 2394 +Name = "a blue pillow" +Flags = {Take} +Attributes = {Weight=1600} + +TypeID = 2395 +Name = "a red pillow" +Flags = {Take} +Attributes = {Weight=1600} + +TypeID = 2396 +Name = "a green pillow" +Flags = {Take} +Attributes = {Weight=1600} + +TypeID = 2397 +Name = "a yellow pillow" +Flags = {Take} +Attributes = {Weight=1600} + +TypeID = 2398 +Name = "a round blue pillow" +Flags = {Take} +Attributes = {Weight=1550} + +TypeID = 2399 +Name = "a round red pillow" +Flags = {Take} +Attributes = {Weight=1550} + +TypeID = 2400 +Name = "a round purple pillow" +Flags = {Take} +Attributes = {Weight=1550} + +TypeID = 2401 +Name = "a round turquoise pillow" +Flags = {Take} +Attributes = {Weight=1550} + +TypeID = 2402 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2403 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2404 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2405 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2406 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2407 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2408 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2409 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2410 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2411 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2412 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2413 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2414 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2415 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2416 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2417 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2418 +Name = "a tusk chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2421,DestroyTarget=3136} + +TypeID = 2419 +Name = "a tusk chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2420,DestroyTarget=3136} + +TypeID = 2420 +Name = "a tusk chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2418,DestroyTarget=3136} + +TypeID = 2421 +Name = "a tusk chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2419,DestroyTarget=3136} + +TypeID = 2422 +Name = "an ivory chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2425,DestroyTarget=3136} + +TypeID = 2423 +Name = "an ivory chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2424,DestroyTarget=3136} + +TypeID = 2424 +Name = "an ivory chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2422,DestroyTarget=3136} + +TypeID = 2425 +Name = "an ivory chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2423,DestroyTarget=3136} + +TypeID = 2426 +Name = "a small trunk" +Flags = {Avoid,Destroy,Height} +Attributes = {DestroyTarget=3136} + +TypeID = 2427 +Name = "a wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2428 +Name = "a wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2429 +Name = "a wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2430 +Name = "a wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2431 +Name = "drawers" +Flags = {Container,Rotate,Destroy,Height,Avoid} +Attributes = {Capacity=10,RotateTarget=2434,DestroyTarget=3136} + +TypeID = 2432 +Name = "drawers" +Flags = {Container,Rotate,Destroy,Height,Avoid} +Attributes = {Capacity=10,RotateTarget=2431,DestroyTarget=3136} + +TypeID = 2433 +Name = "drawers" +Flags = {Container,Rotate,Destroy,Height,Avoid} +Attributes = {Capacity=10,RotateTarget=2432,DestroyTarget=3136} + +TypeID = 2434 +Name = "drawers" +Flags = {Container,Rotate,Destroy,Height,Avoid} +Attributes = {Capacity=10,RotateTarget=2433,DestroyTarget=3136} + +TypeID = 2435 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2436 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2437 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2438 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2439 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2440 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2441 +Name = "a dresser" +Flags = {Container,Rotate,Unpass,Destroy,Height} +Attributes = {Capacity=10,RotateTarget=2444,DestroyTarget=3139} + +TypeID = 2442 +Name = "a dresser" +Flags = {Container,Rotate,Unpass,Destroy,Height} +Attributes = {Capacity=10,RotateTarget=2441,DestroyTarget=3139} + +TypeID = 2443 +Name = "a dresser" +Flags = {Container,Rotate,Unpass,Destroy,Height} +Attributes = {Capacity=10,RotateTarget=2442,DestroyTarget=3139} + +TypeID = 2444 +Name = "a dresser" +Flags = {Container,Rotate,Unpass,Destroy,Height} +Attributes = {Capacity=10,RotateTarget=2443,DestroyTarget=3139} + +TypeID = 2445 +Name = "a pendulum clock" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2448,DestroyTarget=3139} + +TypeID = 2446 +Name = "a pendulum clock" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2445,DestroyTarget=3139} + +TypeID = 2447 +Name = "a pendulum clock" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2446,DestroyTarget=3139} + +TypeID = 2448 +Name = "a pendulum clock" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2447,DestroyTarget=3139} + +TypeID = 2449 +Name = "a locker" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2452,DestroyTarget=3140} + +TypeID = 2450 +Name = "a locker" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2451,DestroyTarget=3140} + +TypeID = 2451 +Name = "a locker" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2449,DestroyTarget=3140} + +TypeID = 2452 +Name = "a locker" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2450,DestroyTarget=3140} + +TypeID = 2453 +Name = "a standing mirror" +Description = "You look fine today" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2454,DestroyTarget=3140} + +TypeID = 2454 +Name = "a standing mirror" +Description = "You look fine today" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2453,DestroyTarget=3140} + +TypeID = 2455 +Name = "a bamboo wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2456 +Name = "a bamboo wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2457 +Name = "a bamboo wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2458 +Name = "a bamboo wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2459 +Name = "a bamboo shelf" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2460 +Name = "a bamboo shelf" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2461 +Name = "a small bamboo shelf" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2462 +Name = "a small bamboo shelf" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2463 +Name = "a small bamboo shelf" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2464 +Name = "a small bamboo shelf" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2465 +Name = "a bamboo drawer" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2468,DestroyTarget=3136} + +TypeID = 2466 +Name = "a bamboo drawer" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2465,DestroyTarget=3136} + +TypeID = 2467 +Name = "a bamboo drawer" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2466,DestroyTarget=3136} + +TypeID = 2468 +Name = "a bamboo drawer" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2467,DestroyTarget=3136} + +TypeID = 2469 +Name = "a box" +Flags = {Container,Avoid,Take,Destroy,Height} +Attributes = {Capacity=10,Weight=3500,DestroyTarget=3135} + +TypeID = 2470 +Name = "a box" +Flags = {Container,Unmove,Avoid,Height,Disguise} +Attributes = {Capacity=10,DisguiseTarget=2469} + +TypeID = 2471 +Name = "a crate" +Flags = {Container,Avoid,Take,Destroy,Height} +Attributes = {Capacity=15,Weight=8000,DestroyTarget=3135} + +TypeID = 2472 +Name = "a chest" +Flags = {Container,Take,Rotate,Destroy,Height,Avoid} +Attributes = {Capacity=15,Weight=12000,RotateTarget=2482,DestroyTarget=3137} + +TypeID = 2473 +Name = "a box" +Flags = {Container,Avoid,Take,Destroy,Height} +Attributes = {Capacity=10,Weight=3500,DestroyTarget=3140} + +TypeID = 2474 +Name = "a wooden coffin" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2475 +Name = "a wooden coffin" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2476 +Name = "a wooden coffin" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2477 +Name = "a wooden coffin" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2478 +Name = "a treasure chest" +Flags = {Container,Avoid,Take,Height} +Attributes = {Capacity=10,Weight=9500} + +TypeID = 2479 +Name = "a chest" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2472} + +TypeID = 2480 +Name = "a chest" +Flags = {Container,Take,Rotate,Destroy,Height,Avoid} +Attributes = {Capacity=15,Weight=12000,RotateTarget=2481,DestroyTarget=3137} + +TypeID = 2481 +Name = "a chest" +Flags = {Container,Take,Rotate,Destroy,Height,Avoid} +Attributes = {Capacity=15,Weight=12000,RotateTarget=2472,DestroyTarget=3137} + +TypeID = 2482 +Name = "a chest" +Flags = {Container,Take,Rotate,Destroy,Height,Avoid} +Attributes = {Capacity=15,Weight=12000,RotateTarget=2480,DestroyTarget=3137} + +TypeID = 2483 +Name = "a large trunk" +Flags = {Container,Rotate,Destroy,Height} +Attributes = {Capacity=18,RotateTarget=2486,DestroyTarget=3140} + +TypeID = 2484 +Name = "a large trunk" +Flags = {Container,Unpass,Rotate,Destroy,Height} +Attributes = {Capacity=18,RotateTarget=2485,DestroyTarget=3140} + +TypeID = 2485 +Name = "a large trunk" +Flags = {Container,Unpass,Rotate,Destroy,Height} +Attributes = {Capacity=18,RotateTarget=2483,DestroyTarget=3140} + +TypeID = 2486 +Name = "a large trunk" +Flags = {Container,Unpass,Rotate,Destroy,Height} +Attributes = {Capacity=18,RotateTarget=2484,DestroyTarget=3140} + +TypeID = 2487 +Name = "a bed" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedTarget=2495} + +TypeID = 2488 +Name = "a bed" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedTarget=2496} + +TypeID = 2489 +Name = "a cot" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedTarget=2501} + +TypeID = 2490 +Name = "a cot" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedTarget=2502} + +TypeID = 2491 +Name = "a cot" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedTarget=2499} + +TypeID = 2492 +Name = "a cot" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedTarget=2500} + +TypeID = 2493 +Name = "a bed" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedTarget=2497} + +TypeID = 2494 +Name = "a bed" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedTarget=2498} + +TypeID = 2495 +Name = "a bed" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedFree=2487} + +TypeID = 2496 +Name = "a bed" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedFree=2488} + +TypeID = 2497 +Name = "a bed" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedFree=2493} + +TypeID = 2498 +Name = "a bed" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedFree=2494} + +TypeID = 2499 +Name = "a cot" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedFree=2491} + +TypeID = 2500 +Name = "a cot" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedFree=2492} + +TypeID = 2501 +Name = "a cot" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedFree=2489} + +TypeID = 2502 +Name = "a cot" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedFree=2490} + +TypeID = 2503 +Name = "a hammock" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedTarget=2507} + +TypeID = 2504 +Name = "a hammock" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedTarget=2508} + +TypeID = 2505 +Name = "a hammock" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedTarget=2509} + +TypeID = 2506 +Name = "a hammock" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedTarget=2510} + +TypeID = 2507 +Name = "a hammock" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedFree=2503} + +TypeID = 2508 +Name = "a hammock" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedFree=2504} + +TypeID = 2509 +Name = "a hammock" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedFree=2505} + +TypeID = 2510 +Name = "a hammock" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedFree=2506} + +TypeID = 2511 +Name = "a grass mat" +Flags = {Unmove} + +TypeID = 2512 +Name = "a grass mat" +Flags = {Unmove} + +TypeID = 2513 +Name = "a grass mat" +Flags = {Unmove} + +TypeID = 2514 +Name = "a grass mat" +Flags = {Unmove} + +TypeID = 2515 +Name = "a straw mat" +Flags = {Unmove} + +TypeID = 2516 +Name = "a straw mat" +Flags = {Unmove} + +TypeID = 2517 +Name = "a straw mat" +Flags = {Unmove} + +TypeID = 2518 +Name = "a straw mat" +Flags = {Unmove} + +TypeID = 2519 +Name = "a barrel" +Flags = {Container,Destroy,Height,Avoid} +Attributes = {Capacity=25,DestroyTarget=3138} + +TypeID = 2520 +Name = "a water cask" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 2521 +Name = "a lemonade cask" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=LEMONADE} + +TypeID = 2522 +Name = "a wine cask" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2523 +Name = "a barrel" +Flags = {Container,Destroy,Height,Avoid} +Attributes = {Capacity=25,DestroyTarget=3135} + +TypeID = 2524 +Name = "a trough" +Flags = {MultiUse,FluidContainer,Unpass,Destroy,Height} +Attributes = {DestroyTarget=3135} + +TypeID = 2525 +Name = "a beer cask" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=BEER} + +TypeID = 2526 +Name = "a dustbin" +Flags = {CollisionEvent,Unpass,Unmove} + +TypeID = 2527 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2528 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2529 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2530 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2531 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2532 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2533 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2534 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2535 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2536,Brightness=3,LightColor=199} + +TypeID = 2536 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2535,Brightness=0,LightColor=215} + +TypeID = 2537 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2538,Brightness=3,LightColor=193} + +TypeID = 2538 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2537,Brightness=0,LightColor=215} + +TypeID = 2539 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2540,Brightness=3,LightColor=193} + +TypeID = 2540 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2539,Brightness=0,LightColor=215} + +TypeID = 2541 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2542,Brightness=3,LightColor=193} + +TypeID = 2542 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2541,Brightness=0,LightColor=215} + +TypeID = 2543 +Name = "a box" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2469} + +TypeID = 2544 +Name = "a wooden coffin" +Flags = {Bottom,Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2476} + +TypeID = 2545 +Name = "a wooden coffin" +Flags = {Bottom,Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2474} + +TypeID = 2546 +Name = "a chest" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2472} + +TypeID = 2547 +Name = "a bananapalm" +Flags = {Bottom,Chest,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=3639} + +TypeID = 2548 +Name = "a dead dragon" +Flags = {Chest,Unmove,Disguise} +Attributes = {DisguiseTarget=4025} + +TypeID = 2549 +Name = "a honeyflower patch" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2984} + +TypeID = 2550 +Name = "a dead human" +Flags = {Chest,Unmove,Disguise} +Attributes = {DisguiseTarget=4240} + +TypeID = 2551 +Name = "a box" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2473} + +TypeID = 2552 +Name = "a dead tree" +Flags = {Bottom,Chest,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=3634} + +TypeID = 2553 +Name = "drawers" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2433} + +TypeID = 2554 +Name = "drawers" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2434} + +TypeID = 2555 +Name = "a small hole" +Flags = {Bank,Chest,Unmove,Avoid,Disguise} +Attributes = {Waypoints=130,DisguiseTarget=387} + +TypeID = 2556 +Name = "a loose board" +Flags = {Bank,Chest,Unmove,Avoid,Disguise} +Attributes = {Waypoints=100,DisguiseTarget=408} + +TypeID = 2557 +Name = "a pile of bones" +Flags = {Chest,Unmove,Disguise} +Attributes = {DisguiseTarget=4285} + +TypeID = 2558 +Name = "a bookcase" +Flags = {Chest,Unpass,Unmove,Unlay,Height,Disguise} +Attributes = {DisguiseTarget=2435} + +TypeID = 2559 +Name = "a bookcase" +Flags = {Chest,Unpass,Unmove,Unlay,Height,Disguise} +Attributes = {DisguiseTarget=2438} + +TypeID = 2560 +Name = "a stone coffin" +Flags = {Bottom,Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=1983} + +TypeID = 2561 +Name = "a barrel" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2523} + +TypeID = 2562 +Name = "a hollow stone" +Flags = {Bottom,Chest,Unpass,Unmove,Unlay,Height,Disguise} +Attributes = {DisguiseTarget=1777} + +TypeID = 2563 +Name = "a pile of bones" +Flags = {Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=4305} + +TypeID = 2564 +Name = "a sarcophagus" +Flags = {Bottom,Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=1994} + +TypeID = 2565 +Name = "a sarcophagus" +Flags = {Bottom,Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=1992} + +TypeID = 2566 +Name = "a lever" +Flags = {UseEvent,Unmove,Disguise} +Attributes = {DisguiseTarget=2772} + +TypeID = 2567 +Name = "a lever" +Description = "It doesn't move" +Flags = {UseEvent,Unmove,Expire,Disguise} +Attributes = {ExpireTarget=2566,TotalExpireTime=240,DisguiseTarget=2773} + +TypeID = 2568 +Name = "a cobra statue" +Flags = {Bottom,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=2054} + +TypeID = 2569 +Name = "a lever" +Flags = {UseEvent,Unmove,Disguise} +Attributes = {DisguiseTarget=2772} + +TypeID = 2570 +Name = "a lever" +Flags = {UseEvent,Unmove,Disguise} +Attributes = {DisguiseTarget=2773} + +TypeID = 2571 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay,Expire,Disguise} +Attributes = {ExpireTarget=0,TotalExpireTime=300,DisguiseTarget=1772} + +TypeID = 2572 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2573 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2574 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2575 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2576 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2577 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2578 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2579 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2580 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2581 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2582 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2583 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2584 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2585 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2586 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2587 +Name = "a brown bear fur" +Flags = {Clip,Unmove} + +TypeID = 2588 +Name = "a brown bear fur" +Flags = {Clip,Unmove} + +TypeID = 2589 +Name = "a brown bear fur" +Flags = {Clip,Unmove} + +TypeID = 2590 +Name = "a brown bear fur" +Flags = {Clip,Unmove} + +TypeID = 2591 +Name = "a polar bear fur" +Flags = {Clip,Unmove} + +TypeID = 2592 +Name = "a polar bear fur" +Flags = {Clip,Unmove} + +TypeID = 2593 +Name = "a polar bear fur" +Flags = {Clip,Unmove} + +TypeID = 2594 +Name = "a polar bear fur" +Flags = {Clip,Unmove} + +TypeID = 2595 +Name = "a badger fur" +Flags = {Clip,Unmove} + +TypeID = 2596 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2597 +Name = "a blackboard" +Flags = {Unmove,AllowDistRead} + +TypeID = 2598 +Name = "a blackboard" +Flags = {Text,Write,Unmove,AllowDistRead} +Attributes = {MaxLength=200} + +TypeID = 2599 +Name = "a tapestry" +Flags = {Unmove} + +TypeID = 2600 +Name = "a tapestry" +Flags = {Unmove} + +TypeID = 2601 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2602 +Name = "a blackboard" +Flags = {Unmove,AllowDistRead} + +TypeID = 2603 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2604 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2605 +Name = "a blackboard" +Flags = {Text,Write,Unmove,AllowDistRead} +Attributes = {MaxLength=200} + +TypeID = 2606 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2607 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2608 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2609 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2610 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2611 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2612 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2613 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2614 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2615 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2616 +Name = "a scarab ornament" +Flags = {UseEvent,Unmove} + +TypeID = 2617 +Name = "a scarab ornament" +Flags = {UseEvent,Unmove} + +TypeID = 2618 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2619 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2620 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2621 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2622 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2623 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2624 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2625 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2626 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2627 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2628 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2629 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2630 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2631 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2632 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2633 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2634 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2635 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2636 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2637 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2638 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2639 +Name = "a picture" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2640 +Name = "a picture" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2641 +Name = "a picture" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2642 +Name = "a purple tapestry" +Flags = {Unmove} + +TypeID = 2643 +Name = "a purple tapestry" +Flags = {Unmove} + +TypeID = 2644 +Name = "a purple tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2645 +Name = "a green tapestry" +Flags = {Unmove} + +TypeID = 2646 +Name = "a green tapestry" +Flags = {Unmove} + +TypeID = 2647 +Name = "a green tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2648 +Name = "a yellow tapestry" +Flags = {Unmove} + +TypeID = 2649 +Name = "a yellow tapestry" +Flags = {Unmove} + +TypeID = 2650 +Name = "a yellow tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2651 +Name = "an orange tapestry" +Flags = {Unmove} + +TypeID = 2652 +Name = "an orange tapestry" +Flags = {Unmove} + +TypeID = 2653 +Name = "an orange tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2654 +Name = "a red tapestry" +Flags = {Unmove} + +TypeID = 2655 +Name = "a red tapestry" +Flags = {Unmove} + +TypeID = 2656 +Name = "a red tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2657 +Name = "a blue tapestry" +Flags = {Unmove} + +TypeID = 2658 +Name = "a blue tapestry" +Flags = {Unmove} + +TypeID = 2659 +Name = "a blue tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2660 +Name = "a cuckoo clock" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2661,TotalExpireTime=595} + +TypeID = 2661 +Name = "a cuckoo clock" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2660,TotalExpireTime=5} + +TypeID = 2662 +Name = "a cuckoo clock" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2663,TotalExpireTime=595} + +TypeID = 2663 +Name = "a cuckoo clock" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2663,TotalExpireTime=5} + +TypeID = 2664 +Name = "a cuckoo clock" +Flags = {Take,Hang,Expire} +Attributes = {Weight=800,ExpireTarget=2668,TotalExpireTime=600} + +TypeID = 2665 +Name = "a white tapestry" +Flags = {Unmove} + +TypeID = 2666 +Name = "a tapestry" +Flags = {Unmove} + +TypeID = 2667 +Name = "a white tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2668 +Name = "a cuckoo clock" +Flags = {Take,Hang,Expire} +Attributes = {Weight=800,ExpireTarget=2664,TotalExpireTime=5} + +TypeID = 2669 +Name = "a demon trophy" +Flags = {Unmove} + +TypeID = 2670 +Name = "a demon trophy" +Flags = {Unmove} + +TypeID = 2671 +Name = "a wolf trophy" +Flags = {Unmove} + +TypeID = 2672 +Name = "a wolf trophy" +Flags = {Unmove} + +TypeID = 2673 +Name = "an orc trophy" +Flags = {Unmove} + +TypeID = 2674 +Name = "an orc trophy" +Flags = {Unmove} + +TypeID = 2675 +Name = "a behemoth trophy" +Flags = {Unmove} + +TypeID = 2676 +Name = "a behemoth trophy" +Flags = {Unmove} + +TypeID = 2677 +Name = "a deer trophy" +Flags = {Unmove} + +TypeID = 2678 +Name = "a deer trophy" +Flags = {Unmove} + +TypeID = 2679 +Name = "a cyclops trophy" +Flags = {Unmove} + +TypeID = 2680 +Name = "a cyclops trophy" +Flags = {Unmove} + +TypeID = 2681 +Name = "a dragon lord trophy" +Flags = {Unmove} + +TypeID = 2682 +Name = "a dragon lord trophy" +Flags = {Unmove} + +TypeID = 2683 +Name = "a lion trophy" +Flags = {Unmove} + +TypeID = 2684 +Name = "a lion trophy" +Flags = {Unmove} + +TypeID = 2685 +Name = "a minotaur trophy" +Flags = {Unmove} + +TypeID = 2686 +Name = "a minotaur trophy" +Flags = {Unmove} + +TypeID = 2687 +Name = "a feather decoration" +Flags = {Unmove} + +TypeID = 2688 +Name = "a feather decoration" +Flags = {Unmove} + +TypeID = 2689 +Name = "a dried fur" +Flags = {Unmove} + +TypeID = 2690 +Name = "a dried fur" +Flags = {Unmove} + +TypeID = 2691 +Name = "a dried fur" +Flags = {Unmove} + +TypeID = 2692 +Name = "a dried fur" +Flags = {Unmove} + +TypeID = 2693 +Name = "a bloodspot" +Flags = {Unmove,Hang} + +TypeID = 2694 +Name = "a bloodspot" +Flags = {Unmove,Hang} + +TypeID = 2695 +Name = "a bloodspot" +Flags = {Unmove,Hang} + +TypeID = 2696 +Name = "a bloodspot" +Flags = {Unmove} + +TypeID = 2697 +Name = "a bloodspot" +Flags = {Unmove} + +TypeID = 2698 +Name = "a bloodspot" +Flags = {Unmove} + +TypeID = 2699 +Name = "cobwebs" +Flags = {Unmove,Hang} + +TypeID = 2700 +Name = "cobwebs" +Flags = {Unmove,Hang} + +TypeID = 2701 +Name = "cobwebs" +Flags = {Unmove,Hang} + +TypeID = 2702 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2703 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2704 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2705 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2706 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2707 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2708 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2709 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2710 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2711 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2712 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2713 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2714 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2715 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2716 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2717 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2718 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2719 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2720 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2721 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2722 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2723 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2724 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2725 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2726 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2727 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2728 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2729 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2730 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2731 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2732 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2733 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2734 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2735 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2736 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2737 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2738 +Name = "tanned brown bear fur" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2739 +Name = "tanned brown bear fur" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2740 +Name = "tanned polar bear fur" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2741 +Name = "tanned polar bear fur" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2742 +Name = "a pile of chopped wood" +Flags = {Unpass,Unmove} + +TypeID = 2743 +Name = "a block of wood" +Description = "It's a lumberjack's working place" +Flags = {Unpass,Unmove} + +TypeID = 2744 +Name = "some pieces of wood" +Flags = {Unmove} + +TypeID = 2745 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2746 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2747 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2748 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2749 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2750 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2751 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2752 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2753 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2754 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2755 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2756 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2757 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2758 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2759 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2760 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2761 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2762 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2763 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2764 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2765 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2766 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2767 +Name = "tanned tiger fur" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2768 +Name = "tanned tiger fur" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2769 +Name = "a tiger fur" +Flags = {Unmove,Hang} + +TypeID = 2770 +Name = "a tiger fur" +Flags = {Unmove,Hang} + +TypeID = 2771 +Name = "a sundial" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2772 +Name = "a lever" +Flags = {UseEvent,Unmove} + +TypeID = 2773 +Name = "a lever" +Flags = {UseEvent,Unmove} + +TypeID = 2774 +Name = "a torch bearer" +Flags = {UseEvent,Unmove,Hang,Disguise} +Attributes = {Brightness=0,LightColor=215,DisguiseTarget=2928} + +TypeID = 2775 +Name = "a furniture package" +Description = "It contains a construction kit for a red cushioned chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2776 +Name = "a furniture package" +Description = "It contains a construction kit for a green cushioned chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2777 +Name = "a furniture package" +Description = "It contains a construction kit for a wooden chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2778 +Name = "a furniture package" +Description = "It contains a construction kit for a rocking chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2779 +Name = "a furniture package" +Description = "It contains a construction kit for a sofa chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2780 +Name = "a furniture package" +Description = "It contains a construction kit for a tusk chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2781 +Name = "a furniture package" +Description = "It contains a construction kit for a ivory chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2782 +Name = "a furniture package" +Description = "It contains a construction kit for a small table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2783 +Name = "a furniture package" +Description = "It contains a construction kit for a round table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2784 +Name = "a furniture package" +Description = "It contains a construction kit for a square table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2785 +Name = "a furniture package" +Description = "It contains a construction kit for a big table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2786 +Name = "a furniture package" +Description = "It contains a construction kit for a stone table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2787 +Name = "a furniture package" +Description = "It contains a construction kit for a tusk table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2788 +Name = "a furniture package" +Description = "It contains a construction kit for a bamboo table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2789 +Name = "a furniture package" +Description = "It contains a construction kit for a drawer" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2790 +Name = "a furniture package" +Description = "It contains a construction kit for a dresser" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2791 +Name = "a furniture package" +Description = "It contains a construction kit for a locker" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2792 +Name = "a furniture package" +Description = "It contains a construction kit for a trough" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2793 +Name = "a furniture package" +Description = "It contains a construction kit for a barrel" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2794 +Name = "a furniture package" +Description = "It contains a construction kit for a trunk" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2795 +Name = "a furniture package" +Description = "It contains a construction kit for a bamboo drawer" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2796 +Name = "a furniture package" +Description = "It contains a construction kit for a birdcage" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2000} + +TypeID = 2797 +Name = "a furniture package" +Description = "It contains a construction kit for a globe" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2000} + +TypeID = 2798 +Name = "a furniture package" +Description = "It contains a construction kit for a table lamp" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2000} + +TypeID = 2799 +Name = "a furniture package" +Description = "It contains a construction kit for a telescope" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2800 +Name = "a furniture package" +Description = "It contains a construction kit for a rocking horse" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2801 +Name = "a furniture package" +Description = "It contains a construction kit for a pendulum clock" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2802 +Name = "a furniture package" +Description = "It contains a construction kit for a knight statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2803 +Name = "a furniture package" +Description = "It contains a construction kit for a minotaur statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2804 +Name = "a furniture package" +Description = "It contains a construction kit for a goblin statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2805 +Name = "a furniture package" +Description = "It contains a construction kit for a large amphora" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2806 +Name = "a furniture package" +Description = "It contains a construction kit for a coal basin" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2807 +Name = "a furniture package" +Description = "It contains a construction kit for a piano" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2808 +Name = "a furniture package" +Description = "It contains a construction kit for a harp" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2809 +Name = "a furniture package" +Description = "It contains a construction kit for a trunk chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2810 +Name = "a furniture package" +Description = "It contains a construction kit for a trunk table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2811 +Name = "a furniture package" +Description = "It contains an indoor plant" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2812 +Name = "a furniture package" +Description = "It contains a christmas tree" +Flags = {UseEvent,Avoid,Take,Expire,Height} +Attributes = {Weight=3500,ExpireTarget=0,TotalExpireTime=21600} + +TypeID = 2813 +Name = "a blank paper" +Description = "It is rewriteable" +Flags = {Text,Write,Take} +Attributes = {MaxLength=512,Weight=50} + +TypeID = 2814 +Name = "a parchment" +Description = "It is rewriteable" +Flags = {Text,Write,Take} +Attributes = {MaxLength=1024,Weight=200} + +TypeID = 2815 +Name = "a scroll" +Description = "It is rewriteable" +Flags = {Text,Write,Take} +Attributes = {MaxLength=512,Weight=50} + +TypeID = 2816 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2817 +Name = "a blank parchment" +Description = "It is rewriteable" +Flags = {Text,Write,Take} +Attributes = {MaxLength=1024,Weight=200} + +TypeID = 2818 +Name = "a document" +Description = "It is rewriteable" +Flags = {Text,Write,Take} +Attributes = {MaxLength=1024,Weight=150} + +TypeID = 2819 +Name = "a parchment" +Flags = {Text,Take} +Attributes = {Weight=200} + +TypeID = 2820 +Name = "a paper" +Flags = {Text,Take} +Attributes = {Weight=100} + +TypeID = 2821 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2822 +Name = "a map" +Flags = {Text,Take} +Attributes = {Weight=830} + +TypeID = 2823 +Name = "a map" +Flags = {Text,Take} +Attributes = {Weight=790} + +TypeID = 2824 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2825 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2826 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2827 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2828 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2829 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2830 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2831 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2832 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2833 +Name = "a parchment" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=200} + +TypeID = 2834 +Name = "a document" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=150} + +TypeID = 2835 +Name = "a parchment" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=200} + +TypeID = 2836 +Name = "the holy Tible" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2837 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2838 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2839 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2840 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2841 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2842 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2843 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2844 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2845 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2846 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2847 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2848 +Name = "a purple tome" +Description = "It's a volume of The Mystic Secrets of Tibia" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 2849 +Name = "a green tome" +Description = "It's a volume of The Mystic Secrets of Tibia" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 2850 +Name = "a blue tome" +Description = "It's a volume of The Mystic Secrets of Tibia" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 2851 +Name = "a grey tome" +Description = "It's a volume of The Mystic Secrets of Tibia" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 2852 +Name = "a red tome" +Description = "It's a volume of The Mystic Secrets of Tibia" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 2853 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2854 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2855 +Name = "a basket" +Flags = {Container,Take} +Attributes = {Capacity=5,Weight=950} + +TypeID = 2856 +Name = "a present" +Flags = {Container,Take} +Attributes = {Capacity=5,Weight=600} + +TypeID = 2857 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2858 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2859 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2860 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2861 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2862 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2863 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2864 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2865 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2866 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2867 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2868 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2869 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2870 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2871 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2872 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2873 +Name = "a bucket" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=2000} + +TypeID = 2874 +Name = "a vial" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=180} + +TypeID = 2875 +Name = "a bottle" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=250} + +TypeID = 2876 +Name = "a vase" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=940} + +TypeID = 2877 +Name = "a green flask" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=300} + +TypeID = 2878 +Name = "a broken flask" +Flags = {Take} +Attributes = {Weight=250} + +TypeID = 2879 +Name = "an elven vase" +Description = "It is made of very fine glass and covered with decorations" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=940} + +TypeID = 2880 +Name = "a mug" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=250} + +TypeID = 2881 +Name = "a cup" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=200} + +TypeID = 2882 +Name = "a jug" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=750} + +TypeID = 2883 +Name = "a cup" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=200} + +TypeID = 2884 +Name = "a cup" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=200} + +TypeID = 2885 +Name = "a brown flask" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=300} + +TypeID = 2886 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=2887,TotalExpireTime=120} + +TypeID = 2887 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=2888,TotalExpireTime=120} + +TypeID = 2888 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=120} + +TypeID = 2889 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=2890,TotalExpireTime=120} + +TypeID = 2890 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=2891,TotalExpireTime=120} + +TypeID = 2891 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=120} + +TypeID = 2892 +Name = "a broken bottle" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 2893 +Name = "an amphora" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=9700} + +TypeID = 2894 +Name = "a broken flask" +Flags = {Take} +Attributes = {Weight=250} + +TypeID = 2895 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2896 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2897 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2898 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2899 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2900 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2901 +Name = "a waterskin" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=700} + +TypeID = 2902 +Name = "a bowl" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=380} + +TypeID = 2903 +Name = "a golden mug" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=470} + +TypeID = 2904 +Name = "a large amphora" +Flags = {MultiUse,FluidContainer,Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3144} + +TypeID = 2905 +Name = "a plate" +Flags = {Take} +Attributes = {Weight=150} + +TypeID = 2906 +Name = "a watch" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 2907 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2908,Brightness=0,LightColor=0} + +TypeID = 2908 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2907,Brightness=6,LightColor=206} + +TypeID = 2909 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2910,Brightness=0,LightColor=0} + +TypeID = 2910 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2909,Brightness=6,LightColor=206} + +TypeID = 2911 +Name = "a candelabrum" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2912,Weight=5000,Brightness=0,LightColor=0} + +TypeID = 2912 +Name = "a lit candelabrum" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2911,Weight=5000,Brightness=6,LightColor=206,ExpireTarget=2913,TotalExpireTime=3000} + +TypeID = 2913 +Name = "a used candelabrum" +Flags = {Take} +Attributes = {Weight=4500,Brightness=0,LightColor=0} + +TypeID = 2914 +Name = "a lamp" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2915,Weight=3000,Brightness=0,LightColor=0} + +TypeID = 2915 +Name = "a lit lamp" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2914,Weight=3000,Brightness=6,LightColor=199,ExpireTarget=2916,TotalExpireTime=2000} + +TypeID = 2916 +Name = "a used lamp" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=3000,Brightness=0,LightColor=0} + +TypeID = 2917 +Name = "a candlestick" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2918,Weight=300,Brightness=0,LightColor=0} + +TypeID = 2918 +Name = "a lit candlestick" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2917,Weight=300,Brightness=4,LightColor=206,ExpireTarget=2919,TotalExpireTime=3000} + +TypeID = 2919 +Name = "a used candlestick" +Flags = {Take} +Attributes = {Weight=250,Brightness=0,LightColor=0} + +TypeID = 2920 +Name = "a torch" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2921,Weight=500,Brightness=0,LightColor=215} + +TypeID = 2921 +Name = "a lit torch" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2920,Weight=500,Brightness=7,LightColor=206,ExpireTarget=2923,TotalExpireTime=600} + +TypeID = 2922 +Name = "a torch" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2923,Weight=450,Brightness=0,LightColor=215} + +TypeID = 2923 +Name = "a lit torch" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2922,Weight=450,Brightness=6,LightColor=206,ExpireTarget=2925,TotalExpireTime=300} + +TypeID = 2924 +Name = "a torch" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2925,Weight=400,Brightness=0,LightColor=215} + +TypeID = 2925 +Name = "a lit torch" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2924,Weight=400,Brightness=5,LightColor=206,ExpireTarget=2926,TotalExpireTime=300} + +TypeID = 2926 +Name = "a burnt down torch" +Flags = {Take,Expire} +Attributes = {Weight=350,Brightness=0,LightColor=215,ExpireTarget=0,TotalExpireTime=300} + +TypeID = 2927 +Name = "a lit candelabrum" +Flags = {ChangeUse,Take} +Attributes = {ChangeTarget=2911,Weight=5000,Brightness=6,LightColor=206} + +TypeID = 2928 +Name = "a torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2929,Brightness=0,LightColor=0} + +TypeID = 2929 +Name = "a lit torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2928,Brightness=6,LightColor=206} + +TypeID = 2930 +Name = "a torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2931,Brightness=0,LightColor=0} + +TypeID = 2931 +Name = "a lit torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2930,Brightness=6,LightColor=206} + +TypeID = 2932 +Name = "an oil lamp" +Flags = {Take,ExpireStop} +Attributes = {Weight=1400,Brightness=0,LightColor=204} + +TypeID = 2933 +Name = "a small oil lamp" +Flags = {Take,ExpireStop} +Attributes = {Weight=900,Brightness=0,LightColor=204} + +TypeID = 2934 +Name = "a tablelamp" +Flags = {ChangeUse} +Attributes = {ChangeTarget=2935} + +TypeID = 2935 +Name = "a lit tablelamp" +Flags = {ChangeUse} +Attributes = {ChangeTarget=2934,Brightness=4,LightColor=207} + +TypeID = 2936 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2937,Brightness=0,LightColor=0} + +TypeID = 2937 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2936,Brightness=6,LightColor=207} + +TypeID = 2938 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2939,Brightness=0,LightColor=0} + +TypeID = 2939 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2938,Brightness=6,LightColor=207} + +TypeID = 2940 +Name = "a torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2941,Brightness=0,LightColor=0} + +TypeID = 2941 +Name = "a lit torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2940,Brightness=6,LightColor=206} + +TypeID = 2942 +Name = "a torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2943,Brightness=0,LightColor=0} + +TypeID = 2943 +Name = "a lit torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2942,Brightness=6,LightColor=206} + +TypeID = 2944 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2945,Brightness=0,LightColor=0} + +TypeID = 2945 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2944,Brightness=6,LightColor=207} + +TypeID = 2946 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2947,Brightness=0,LightColor=0} + +TypeID = 2947 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2946,Brightness=6,LightColor=207} + +TypeID = 2948 +Name = "a wooden flute" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 2949 +Name = "a lyre" +Flags = {UseEvent,Take} +Attributes = {Weight=1250} + +TypeID = 2950 +Name = "a lute" +Flags = {UseEvent,Take} +Attributes = {Weight=3400} + +TypeID = 2951 +Name = "a bongo drum" +Flags = {Take} +Attributes = {Weight=2900} + +TypeID = 2952 +Name = "a drum" +Flags = {UseEvent,Take} +Attributes = {Weight=3200} + +TypeID = 2953 +Name = "panpipes" +Flags = {UseEvent,Take} +Attributes = {Weight=820} + +TypeID = 2954 +Name = "a simple fanfare" +Flags = {UseEvent,Take} +Attributes = {Weight=2200} + +TypeID = 2955 +Name = "a fanfare" +Flags = {UseEvent,Take} +Attributes = {Weight=2300} + +TypeID = 2956 +Name = "a royal fanfare" +Flags = {UseEvent,Take} +Attributes = {Weight=2500} + +TypeID = 2957 +Name = "a post horn" +Description = "It's property of the Postmaster's Guild and only rewarded to loyal members" +Flags = {UseEvent,Take} +Attributes = {Weight=1500} + +TypeID = 2958 +Name = "a war horn" +Flags = {UseEvent,Take} +Attributes = {Weight=1500} + +TypeID = 2959 +Name = "a piano" +Flags = {UseEvent,Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2962,DestroyTarget=3139} + +TypeID = 2960 +Name = "a piano" +Flags = {UseEvent,Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2961,DestroyTarget=3139} + +TypeID = 2961 +Name = "a piano" +Flags = {UseEvent,Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2959,DestroyTarget=3139} + +TypeID = 2962 +Name = "a piano" +Flags = {UseEvent,Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2960,DestroyTarget=3139} + +TypeID = 2963 +Name = "a harp" +Flags = {UseEvent,Unpass,Rotate,Destroy} +Attributes = {RotateTarget=2964,DestroyTarget=3136} + +TypeID = 2964 +Name = "a harp" +Flags = {UseEvent,Unpass,Rotate,Destroy} +Attributes = {RotateTarget=2963,DestroyTarget=3136} + +TypeID = 2965 +Name = "a didgeridoo" +Flags = {UseEvent,Take} +Attributes = {Weight=4200} + +TypeID = 2966 +Name = "a war drum" +Flags = {Take} +Attributes = {Weight=3000} + +TypeID = 2967 +Name = "a magical key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2968 +Name = "a wooden key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2969 +Name = "a silver key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2970 +Name = "a copper key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2971 +Name = "a crystal key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2972 +Name = "a golden key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2973 +Name = "a bone key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2974 +Name = "a water pipe" +Flags = {UseEvent,Take,Destroy} +Attributes = {Weight=6500,DestroyTarget=3143} + +TypeID = 2975 +Name = "a birdcage" +Description = "The poor bird seems to have died from a heart attack" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3140} + +TypeID = 2976 +Name = "a birdcage" +Description = "You see a little bird inside" +Flags = {UseEvent,Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3140} + +TypeID = 2977 +Name = "a pumpkinhead" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=950} + +TypeID = 2978 +Name = "a pumpkinhead" +Flags = {Take,Expire} +Attributes = {Weight=1250,Brightness=3,LightColor=200,ExpireTarget=2977,TotalExpireTime=3000} + +TypeID = 2979 +Name = "a globe" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3143} + +TypeID = 2980 +Name = "a water pipe" +Flags = {Take,Destroy} +Attributes = {Weight=5600,DestroyTarget=3143} + +TypeID = 2981 +Name = "god flowers" +Flags = {Avoid,Take} +Attributes = {Weight=1100} + +TypeID = 2982 +Name = "an indoor plant" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3144} + +TypeID = 2983 +Name = "a flower bowl" +Flags = {Avoid,Take} +Attributes = {Weight=1100} + +TypeID = 2984 +Name = "a honey flower" +Flags = {Avoid,Take} +Attributes = {Weight=1000} + +TypeID = 2985 +Name = "a potted flower" +Flags = {Avoid,Take,Destroy} +Attributes = {Weight=2300,DestroyTarget=3144} + +TypeID = 2986 +Name = "a christmas tree" +Flags = {Unpass,Unlay,Destroy,Expire} +Attributes = {DestroyTarget=3140,Brightness=4,LightColor=204,ExpireTarget=0,TotalExpireTime=21600} + +TypeID = 2987 +Name = "a potted flower" +Flags = {Avoid,Take,Destroy} +Attributes = {Weight=2300,DestroyTarget=3144} + +TypeID = 2988 +Name = "exotic flowers" +Flags = {Avoid,Take} +Attributes = {Weight=1100} + +TypeID = 2989 +Name = "a wooden doll" +Flags = {Take} +Attributes = {Weight=860} + +TypeID = 2990 +Name = "a football" + +TypeID = 2991 +Name = "a doll" +Flags = {Take} +Attributes = {Weight=650} + +TypeID = 2992 +Name = "a snowball" +Flags = {Cumulative,Take,Distance} +Attributes = {Weight=80,Range=7,Attack=0,Defense=0,MissileEffect=13,Fragility=100} + +TypeID = 2993 +Name = "a teddy bear" +Flags = {Take} +Attributes = {Weight=590} + +TypeID = 2994 +Name = "a model ship" +Flags = {Take} +Attributes = {Weight=1300} + +TypeID = 2995 +Name = "a piggy bank" +Flags = {Take} +Attributes = {Weight=750} + +TypeID = 2996 +Name = "a broken piggy bank" +Flags = {Take} +Attributes = {Weight=750} + +TypeID = 2997 +Name = "a rocking horse" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=2998,DestroyTarget=3137} + +TypeID = 2998 +Name = "a rocking horse" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=2999,DestroyTarget=3137} + +TypeID = 2999 +Name = "a rocking horse" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=3000,DestroyTarget=3137} + +TypeID = 3000 +Name = "a rocking horse" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=2997,DestroyTarget=3137} + +TypeID = 3001 +Name = "a bear doll" +Flags = {Take} +Attributes = {Weight=590} + +TypeID = 3002 +Name = "a voodoo doll" +Flags = {Take} +Attributes = {Weight=650} + +TypeID = 3003 +Name = "a rope" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=1800} + +TypeID = 3004 +Name = "a wedding ring" +Flags = {Take} +Attributes = {Weight=40,SlotType=RING} + +TypeID = 3005 +Name = "an elven brooch" +Flags = {Take} +Attributes = {Weight=90} + +TypeID = 3006 +Name = "a ring of the sky" +Flags = {Take} +Attributes = {Weight=40,SlotType=RING} + +TypeID = 3007 +Name = "a crystal ring" +Description = "The magical ring will convert the gold you touch" +Flags = {Take,ShowDetail} +Attributes = {Weight=90,SlotType=RING,TotalUses=100} + +TypeID = 3008 +Name = "a crystal necklace" +Flags = {Take} +Attributes = {Weight=490,SlotType=NECKLACE} + +TypeID = 3009 +Name = "a bronze necklace" +Flags = {Take} +Attributes = {Weight=410,SlotType=NECKLACE} + +TypeID = 3010 +Name = "an emerald bangle" +Flags = {Take} +Attributes = {Weight=170} + +TypeID = 3011 +Name = "a crown" +Flags = {Take} +Attributes = {Weight=1900,SlotType=HEAD} + +TypeID = 3012 +Name = "a wolf tooth chain" +Flags = {Take} +Attributes = {Weight=330,SlotType=NECKLACE} + +TypeID = 3013 +Name = "a golden amulet" +Description = "Many gems glitter on the amulet" +Flags = {Take} +Attributes = {Weight=830,SlotType=NECKLACE} + +TypeID = 3014 +Name = "a star amulet" +Flags = {Take} +Attributes = {Weight=610,SlotType=NECKLACE} + +TypeID = 3015 +Name = "a silver necklace" +Flags = {Take} +Attributes = {Weight=480,SlotType=NECKLACE} + +TypeID = 3016 +Name = "a ruby necklace" +Flags = {Take} +Attributes = {Weight=570,SlotType=NECKLACE} + +TypeID = 3017 +Name = "a silver brooch" +Flags = {Take} +Attributes = {Weight=110} + +TypeID = 3018 +Name = "a scarab amulet" +Flags = {Take} +Attributes = {Weight=770,SlotType=NECKLACE} + +TypeID = 3019 +Name = "a demonbone amulet" +Flags = {Take} +Attributes = {Weight=690,SlotType=NECKLACE} + +TypeID = 3020 +Name = "some golden fruits" +Flags = {Take} +Attributes = {Weight=1070} + +TypeID = 3021 +Name = "a saphire amulet" +Flags = {Take} +Attributes = {Weight=680,SlotType=NECKLACE} + +TypeID = 3022 +Name = "an ancient tiara" +Flags = {Take} +Attributes = {Weight=820,SlotType=HEAD} + +TypeID = 3023 +Name = "a holy scarab" +Flags = {Take} +Attributes = {Weight=870} + +TypeID = 3024 +Name = "a holy falcon" +Flags = {Take} +Attributes = {Weight=840} + +TypeID = 3025 +Name = "an ancient amulet" +Flags = {Take} +Attributes = {Weight=840,SlotType=NECKLACE} + +TypeID = 3026 +Name = "a white pearl" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3027 +Name = "a black pearl" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3028 +Name = "a small diamond" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3029 +Name = "a small sapphire" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3030 +Name = "a small ruby" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3031 +Name = "a gold coin" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3032 +Name = "a small emerald" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3033 +Name = "a small amethyst" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3034 +Name = "a talon" +Description = "There are many rumours about these magic gems" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3035 +Name = "a platinum coin" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3036 +Name = "a violet gem" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3037 +Name = "a yellow gem" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3038 +Name = "a green gem" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3039 +Name = "a red gem" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3040 +Name = "a gold nugget" +Flags = {Cumulative,Take} +Attributes = {Weight=80} + +TypeID = 3041 +Name = "a blue gem" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3042 +Name = "a scarab coin" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3043 +Name = "a crystal coin" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3044 +Name = "an elephant tusk" +Flags = {Cumulative,Take} +Attributes = {Weight=1000} + +TypeID = 3045 +Name = "a strange talisman" +Flags = {Take,ShowDetail} +Attributes = {Weight=290,SlotType=NECKLACE,AbsorbEnergy=10,ExpireTarget=0,TotalUses=200} + +TypeID = 3046 +Name = "a magic light wand" +Flags = {ChangeUse,Take,ExpireStop,ShowDetail} +Attributes = {ChangeTarget=3047,Weight=1500,Brightness=0,LightColor=215} + +TypeID = 3047 +Name = "a magic light wand" +Description = "The wand glows" +Flags = {ChangeUse,Take,Expire,ShowDetail} +Attributes = {ChangeTarget=3046,Weight=1500,Brightness=8,LightColor=209,ExpireTarget=0,TotalExpireTime=3000} + +TypeID = 3048 +Name = "a might ring" +Flags = {Take,ShowDetail} +Attributes = {Weight=100,SlotType=RING,AbsorbPhysical=25,AbsorbMagic=25,ExpireTarget=0,TotalUses=20} + +TypeID = 3049 +Name = "a stealth ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=100,SlotType=RING,EquipTarget=3086} + +TypeID = 3050 +Name = "a power ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=80,SlotType=RING,EquipTarget=3087} + +TypeID = 3051 +Name = "an energy ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=80,SlotType=RING,EquipTarget=3088} + +TypeID = 3052 +Name = "a life ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=80,SlotType=RING,EquipTarget=3089} + +TypeID = 3053 +Name = "a time ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=90,SlotType=RING,EquipTarget=3090} + +TypeID = 3054 +Name = "a silver amulet" +Flags = {Take,ShowDetail} +Attributes = {Weight=500,SlotType=NECKLACE,AbsorbPoison=10,ExpireTarget=0,TotalUses=200} + +TypeID = 3055 +Name = "a platinum amulet" +Description = "It is an amulet of protection" +Flags = {Take,Armor} +Attributes = {Weight=600,SlotType=NECKLACE,ArmorValue=2} + +TypeID = 3056 +Name = "a bronze amulet" +Flags = {Take,ShowDetail} +Attributes = {Weight=500,SlotType=NECKLACE,AbsorbManaDrain=15,ExpireTarget=0,TotalUses=200} + +TypeID = 3057 +Name = "an amulet of loss" +Flags = {Take} +Attributes = {Weight=420,SlotType=NECKLACE} + +TypeID = 3058 +Name = "a strange symbol" +Flags = {MultiUse,Take} +Attributes = {Weight=200,Brightness=2,LightColor=215} + +TypeID = 3059 +Name = "a spellbook" +Flags = {Text,Take} +Attributes = {Weight=5800} + +TypeID = 3060 +Name = "an orb" +Flags = {Take} +Attributes = {Weight=800,Brightness=2,LightColor=26} + +TypeID = 3061 +Name = "a life crystal" +Flags = {Take} +Attributes = {Weight=250,Brightness=2,LightColor=29} + +TypeID = 3062 +Name = "a mind stone" +Flags = {MultiUse,Take} +Attributes = {Weight=250} + +TypeID = 3063 +Name = "a gold ring" +Flags = {Take} +Attributes = {Weight=100,SlotType=RING} + +TypeID = 3064 +Name = "the orb of nature" +Flags = {Unmove} + +TypeID = 3065 +Name = "a quagmire rod" +Description = "It emits clouds of poisonous swamp gas" +Flags = {Take,Wand} +Attributes = {MinimumLevel=26,Weight=2650,Brightness=2,LightColor=67,Vocations=2,Range=3,ManaConsumption=8,AttackStrength=45,AttackVariation=8,DamageType=Poison,MissileEffect=15} + +TypeID = 3066 +Name = "a snakebite rod" +Description = "It seems to twitch and quiver as if trying to escape your grip. The rod has magical powers inside and requires no mana consumption" +Flags = {Take,Wand} +Attributes = {MinimumLevel=7,Weight=4300,Vocations=2,Range=3,AttackStrength=13,AttackVariation=5,DamageType=Poison,MissileEffect=15} + +TypeID = 3067 +Name = "a tempest rod" +Description = "It grants you the power of striking your foes with furious thunderstorms" +Flags = {Take,Wand} +Attributes = {MinimumLevel=33,Weight=2100,Brightness=3,LightColor=29,Vocations=2,ManaConsumption=13,AttackStrength=65,AttackVariation=9,DamageType=Energy,Range=3,MissileEffect=5} + +TypeID = 3068 +Name = "a crystal wand" +Flags = {Take} +Attributes = {Weight=2800} + +TypeID = 3069 +Name = "a volcanic rod" +Description = "It erupts powerful bursts of magma upon everything in your path" +Flags = {Take,Wand} +Attributes = {MinimumLevel=19,Weight=2900,Brightness=2,LightColor=199,Vocations=2,ManaConsumption=5,AttackStrength=30,AttackVariation=7,DamageType=Fire,Range=3,MissileEffect=4} + +TypeID = 3070 +Name = "a moonlight rod" +Description = "Shimmering rays of moonlight radiate from its tip" +Flags = {Take,Wand} +Attributes = {MinimumLevel=13,Weight=1950,Brightness=3,LightColor=143,Vocations=2,ManaConsumption=3,AttackStrength=19,AttackVariation=6,DamageType=Energy,Range=3,MissileEffect=5} + +TypeID = 3071 +Name = "a wand of inferno" +Description = "It unleashes the very fires of hell" +Flags = {Take,Wand} +Attributes = {MinimumLevel=33,Weight=3050,Brightness=3,LightColor=205,Vocations=1,ManaConsumption=13,AttackStrength=65,AttackVariation=9,DamageType=Fire,Range=3,MissileEffect=4} + +TypeID = 3072 +Name = "a wand of plague" +Description = "Infectious goo covers its tip" +Flags = {Take,Wand} +Attributes = {MinimumLevel=19,Weight=2300,Brightness=2,LightColor=67,Vocations=1,ManaConsumption=5,AttackStrength=30,AttackVariation=7,DamageType=Poison,Range=3,MissileEffect=15} + +TypeID = 3073 +Name = "a wand of cosmic energy" +Description = "The energy of a radiant star is trapped inside its globe" +Flags = {Take,Wand} +Attributes = {MinimumLevel=26,Weight=2300,Brightness=2,LightColor=205,Vocations=1,ManaConsumption=8,AttackStrength=45,AttackVariation=8,DamageType=Energy,Range=3,MissileEffect=5} + +TypeID = 3074 +Name = "a wand of vortex" +Description = "Surges of energy rush through the tip of this wand. The wand has magical powers inside and requires no mana consumption" +Flags = {Take,Wand} +Attributes = {MinimumLevel=7,Weight=2300,Brightness=2,LightColor=23,Vocations=1,AttackStrength=13,AttackVariation=5,DamageType=Energy,Range=3,MissileEffect=5} + +TypeID = 3075 +Name = "a wand of dragonbreath" +Description = "Legends say that this wand holds the soul of a young dragon" +Flags = {Take,Wand} +Attributes = {MinimumLevel=13,Weight=2300,Brightness=2,LightColor=192,Vocations=1,ManaConsumption=3,AttackStrength=19,AttackVariation=6,DamageType=Fire,Range=3,MissileEffect=4} + +TypeID = 3076 +Name = "a crystal ball" +Flags = {Take} +Attributes = {Weight=3400} + +TypeID = 3077 +Name = "an ankh" +Flags = {MultiUse,Take} +Attributes = {Weight=420} + +TypeID = 3078 +Name = "a mysterious fetish" +Flags = {MultiUse,Take} +Attributes = {Weight=490} + +TypeID = 3079 +Name = "boots of haste" +Flags = {Take} +Attributes = {Weight=750,SlotType=FEET,SpeedBoost=20} + +TypeID = 3080 +Name = "a broken amulet" +Flags = {Take} +Attributes = {Weight=420,SlotType=NECKLACE} + +TypeID = 3081 +Name = "a stone skin amulet" +Flags = {Take,ShowDetail} +Attributes = {Weight=700,SlotType=NECKLACE,AbsorbPhysical=80,ExpireTarget=0,TotalUses=5} + +TypeID = 3082 +Name = "an elven amulet" +Flags = {Take,ShowDetail} +Attributes = {Weight=270,SlotType=NECKLACE,AbsorbPhysical=10,AbsorbMagic=10,ExpireTarget=0,TotalUses=50} + +TypeID = 3083 +Name = "a garlic necklace" +Flags = {Take,ShowDetail} +Attributes = {Weight=380,SlotType=NECKLACE,AbsorbLifeDrain=20,ExpireTarget=0,TotalUses=150} + +TypeID = 3084 +Name = "a protection amulet" +Flags = {Take,ShowDetail} +Attributes = {Weight=550,SlotType=NECKLACE,AbsorbPhysical=6,ExpireTarget=0,TotalUses=250} + +TypeID = 3085 +Name = "a dragon necklace" +Flags = {Take,ShowDetail} +Attributes = {Weight=630,SlotType=NECKLACE,AbsorbFire=8,ExpireTarget=0,TotalUses=200} + +TypeID = 3086 +Name = "a stealth ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=100,SlotType=RING,Invisible=1,ExpireTarget=0,TotalExpireTime=600,DeEquipTarget=3049} + +TypeID = 3087 +Name = "a power ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=80,SlotType=RING,FistBoost=6,ExpireTarget=0,TotalExpireTime=1800,DeEquipTarget=3050} + +TypeID = 3088 +Name = "an energy ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=80,SlotType=RING,ManaShield=1,ExpireTarget=0,TotalExpireTime=600,DeEquipTarget=3051} + +TypeID = 3089 +Name = "a life ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=80,SlotType=RING,HealthTicks=6000,HealthGain=2,ManaTicks=6000,ManaGain=8,ExpireTarget=0,TotalExpireTime=1200,DeEquipTarget=3052} + +TypeID = 3090 +Name = "a time ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=90,SlotType=RING,SpeedBoost=30,ExpireTarget=0,TotalExpireTime=600,DeEquipTarget=3053} + +TypeID = 3091 +Name = "a sword ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=90,SlotType=RING,EquipTarget=3094} + +TypeID = 3092 +Name = "an axe ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=90,SlotType=RING,EquipTarget=3095} + +TypeID = 3093 +Name = "a club ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=90,SlotType=RING,EquipTarget=3096} + +TypeID = 3094 +Name = "a sword ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=90,SlotType=RING,SwordBoost=4,ExpireTarget=0,TotalExpireTime=1800,DeEquipTarget=3091} + +TypeID = 3095 +Name = "an axe ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=90,SlotType=RING,AxeBoost=4,ExpireTarget=0,TotalExpireTime=1800,DeEquipTarget=3092} + +TypeID = 3096 +Name = "a club ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=90,SlotType=RING,ClubBoost=4,ExpireTarget=0,TotalExpireTime=1800,DeEquipTarget=3093} + +TypeID = 3097 +Name = "a dwarven ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=110,SlotType=RING,EquipTarget=3099} + +TypeID = 3098 +Name = "a ring of healing" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=80,SlotType=RING,EquipTarget=3100} + +TypeID = 3099 +Name = "a dwarven ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=110,SlotType=RING,SuppressDrunk=1,ExpireTarget=0,TotalExpireTime=3600,DeEquipTarget=3097} + +TypeID = 3100 +Name = "a ring of healing" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=80,SlotType=RING,HealthTicks=6000,HealthGain=6,ManaTicks=6000,ManaGain=24,ExpireTarget=0,TotalExpireTime=450,DeEquipTarget=3098} + +TypeID = 3101 +Name = "a screaming spellbook" +Description = "To humble, or not to humble, that is the question" +Flags = {Unmove,Unlay,Unthrow,Unpass,UseEvent} +Attributes = {Weight=5800} + +TypeID = 3102 +Name = "a paw amulet" +Flags = {Take} +Attributes = {Weight=420,SlotType=NECKLACE} + +TypeID = 3103 +Name = "a cornucopia" +Flags = {UseEvent,Take} +Attributes = {Weight=1400} + +TypeID = 3104 +Name = "a banana skin" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3105 +Name = "a dirty fur" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 3106 +Name = "an old twig" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 3107 +Name = "some wood" +Flags = {Take} +Attributes = {Weight=40} + +TypeID = 3108 +Name = "rubbish" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 3109 +Name = "rubbish" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3110 +Name = "a piece of iron" +Flags = {Take} +Attributes = {Weight=20} + +TypeID = 3111 +Name = "a fishbone" +Flags = {Take} +Attributes = {Weight=70} + +TypeID = 3112 +Name = "rotten meat" +Flags = {Take} +Attributes = {Weight=60} + +TypeID = 3113 +Name = "broken pottery" +Flags = {Take} +Attributes = {Weight=180} + +TypeID = 3114 +Name = "a skull" +Flags = {Cumulative,Take} +Attributes = {Weight=2180} + +TypeID = 3115 +Name = "a bone" +Flags = {Take} +Attributes = {Weight=950} + +TypeID = 3116 +Name = "a big bone" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 3117 +Name = "broken brown glass" +Flags = {Take} +Attributes = {Weight=170} + +TypeID = 3118 +Name = "broken green glass" +Flags = {Take} +Attributes = {Weight=170} + +TypeID = 3119 +Name = "a broken sword" +Flags = {Take} +Attributes = {Weight=3500} + +TypeID = 3120 +Name = "a moldy cheese" +Flags = {Take} +Attributes = {Weight=400} + +TypeID = 3121 +Name = "a torn book" +Flags = {Take} +Attributes = {Weight=1100} + +TypeID = 3122 +Name = "a dirty cape" +Flags = {Take} +Attributes = {Weight=2950} + +TypeID = 3123 +Name = "worn leather boots" +Flags = {Take} +Attributes = {Weight=900} + +TypeID = 3124 +Name = "a burnt scroll" +Flags = {Take} +Attributes = {Weight=40} + +TypeID = 3125 +Name = "remains of a fish" +Flags = {Take} +Attributes = {Weight=110} + +TypeID = 3126 +Name = "rubbish" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3127 +Name = "rubbish" +Flags = {Take} +Attributes = {Weight=70} + +TypeID = 3128 +Name = "rubbish" +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 3129 +Name = "some leaves" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3130 +Name = "twigs" +Flags = {Take} +Attributes = {Weight=210} + +TypeID = 3131 +Name = "burnt down firewood" +Flags = {Take} +Attributes = {Weight=420} + +TypeID = 3132 +Name = "an animal skull" +Flags = {Unmove} + +TypeID = 3133 +Name = "humanoid remains" +Flags = {Unmove} + +TypeID = 3134 +Name = "ashes" +Flags = {Unmove} + +TypeID = 3135 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3136 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3137 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3138 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3139 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3140 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3141 +Name = "stone rubbish" +Flags = {Take} +Attributes = {Weight=980} + +TypeID = 3142 +Name = "stone rubbish" +Flags = {Take} +Attributes = {Weight=980} + +TypeID = 3143 +Name = "stone rubbish" +Flags = {Take} +Attributes = {Weight=980} + +TypeID = 3144 +Name = "stone rubbish" +Flags = {Take} +Attributes = {Weight=980} + +TypeID = 3145 +Name = "trashed wooden bars" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2296,TotalExpireTime=120} + +TypeID = 3146 +Name = "trashed wooden bars" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2295,TotalExpireTime=120} + +TypeID = 3147 +Name = "a blank rune" +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 3148 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3149 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3150 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3151 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3152 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3153 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3154 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3155 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3156 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3157 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3158 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3159 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3160 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3161 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3162 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3163 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3164 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3165 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3166 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3167 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3168 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3169 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3170 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3171 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3172 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3173 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3174 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3175 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3176 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3177 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3178 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3179 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3180 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3181 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3182 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3183 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3184 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3185 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3186 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3187 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3188 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3189 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3190 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3191 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3192 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3193 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3194 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3195 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3196 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3197 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3198 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3199 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3200 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3201 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3202 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3203 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3204 +Name = "your own dead body" +Flags = {Unmove} + +TypeID = 3205 +Name = "a family brooch" +Description = "You see the familyname Windtrouser engraved on this brooch" +Flags = {Take} +Attributes = {Weight=110} + +TypeID = 3206 +Name = "a dragonfetish" +Flags = {Take} +Attributes = {Weight=490} + +TypeID = 3207 +Name = "the skull of Ratha" +Flags = {Cumulative,Take} +Attributes = {Weight=2180} + +TypeID = 3208 +Name = "a giant smithhammer" +Description = "This cyclopean hammer seems to be an awesome smithing tool" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6800,WeaponType=CLUB,Attack=24,Defense=14} + +TypeID = 3209 +Name = "a voodoodoll" +Description = "This voodoodoll looks like a little king" +Flags = {Take} +Attributes = {Weight=650} + +TypeID = 3210 +Name = "a hat of the mad" +Description = "You have a vague feeling that it looks somewhat silly" +Flags = {Take,Armor} +Attributes = {Weight=700,SlotType=HEAD,ArmorValue=3} + +TypeID = 3211 +Name = "a witchesbroom" +Description = "Don't use it without flying license. Not suitable for minors" +Flags = {MultiUse,Take} +Attributes = {Weight=1100} + +TypeID = 3212 +Name = "a monks diary" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 3213 +Name = "an annihilation bear" +Description = "I braved the Annihilator and all I got is this lousy teddy bear" +Flags = {Take} +Attributes = {Weight=4300} + +TypeID = 3214 +Name = "a blessed ankh" +Description = "You see the engraving of a white raven on its surface" +Flags = {MultiUse,Take} +Attributes = {Weight=420} + +TypeID = 3215 +Name = "a phoenix egg" +Description = "It seems to be burning from inside" +Flags = {Cumulative,Take} +Attributes = {Weight=30} + +TypeID = 3216 +Name = "a bill" +Description = "This is a bill for an expensive magicians hat and several rabbits" +Flags = {Take} +Attributes = {Weight=150} + +TypeID = 3217 +Name = "a letterbag" +Description = "This bag is nearly bursting from all the letters inside" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=50000,SlotType=BACKPACK} + +TypeID = 3218 +Name = "a present" +Flags = {UseEvent,Take} +Attributes = {Weight=1200} + +TypeID = 3219 +Name = "Waldos Posthorn" +Flags = {UseEvent,Take} +Attributes = {Weight=2200} + +TypeID = 3220 +Name = "a letter to Markwin" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 3221 +Name = "Santa's Mailbox" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3222 +Name = "a helmet ornament" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=160} + +TypeID = 3223 +Name = "a gem holder" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3224 +Name = "a right horn" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=140} + +TypeID = 3225 +Name = "a left horn" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=140} + +TypeID = 3226 +Name = "a damaged helmet" +Description = "This item seems to have several parts missing" +Flags = {Take,Armor} +Attributes = {Weight=1800,SlotType=HEAD,ArmorValue=5} + +TypeID = 3227 +Name = "a helmet piece" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=260} + +TypeID = 3228 +Name = "a helmet adornement" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=180} + +TypeID = 3229 +Name = "a helmet of the ancients" +Description = "The gem of the helmet is burned out and should be replaced" +Flags = {MultiUse,UseEvent,Take,Armor} +Attributes = {Weight=2760,SlotType=HEAD,ArmorValue=8} + +TypeID = 3230 +Name = "a helmet of the ancients" +Description = "The gem is glowing with power" +Flags = {Take,Expire,Armor} +Attributes = {Weight=2760,SlotType=HEAD,ExpireTarget=3229,TotalExpireTime=1800,ArmorValue=11} + +TypeID = 3231 +Name = "a gemmed lamp" +Description = "It is Fa'hradin's enchanted lamp" +Flags = {Take} +Attributes = {Weight=1300} + +TypeID = 3232 +Name = "a spyreport" +Description = "The report is written in some coded language" +Flags = {Take} +Attributes = {Weight=150} + +TypeID = 3233 +Name = "a tear of daraman" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3234 +Name = "a cookbook" +Description = "It contains several exotic recipes" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 3235 +Name = "an ancient rune" +Description = "This rune vibrates with ancient powers. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=300,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3236 +Name = "blue note" +Description = "The blue crystal is softly humming a ghostly melody. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=250,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3237 +Name = "a sword hilt" +Description = "This was once part of a formidable two handed weapon. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=900,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3238 +Name = "a cobrafang dagger" +Description = "This ritual weapon was forged from the sharp fang of a giant cobra. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=600,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3239 +Name = "a crystal arrow" +Description = "This arrow seems not suitable for the use with ordinary bows. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=100,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3240 +Name = "a burning heart" +Description = "The burning heart is still beating with unholy life. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=400,Brightness=1,LightColor=193,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3241 +Name = "an ornamented ankh" +Description = "This ancient relic shows signs of untold age. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=500,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3242 +Name = "a stuffed bunny" +Flags = {Take} +Attributes = {Weight=350} + +TypeID = 3243 +Name = "a gemmed lamp" +Description = "It is the djinn leader's sleeping lamp" +Flags = {Take} +Attributes = {Weight=1300} + +TypeID = 3244 +Name = "an old and used backpack" +Description = "A label on the backpack reads: Property of Sam, Thais" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 3245 +Name = "a ring of wishes" +Description = "(This item has 3 charges left)" +Flags = {Take} +Attributes = {Weight=50,SlotType=RING} + +TypeID = 3246 +Name = "boots of waterwalking" +Description = "(This item has 5 charges left)" +Flags = {Take} +Attributes = {Weight=770,SlotType=FEET} + +TypeID = 3247 +Name = "a djinn's lamp" +Description = "(This item has 2 charges left)" +Flags = {Take} +Attributes = {Weight=600} + +TypeID = 3248 +Name = "a portable hole" +Description = "(This item has 1 charge left)" +Flags = {Unmove} + +TypeID = 3249 +Name = "frozen starlight" +Flags = {Take} +Attributes = {Weight=20,Brightness=6,LightColor=29} + +TypeID = 3250 +Name = "the carrot of doom" +Description = "You can sense the evil power of the carrot" +Flags = {Cumulative,Take} +Attributes = {Nutrition=8,Weight=160} + +TypeID = 3251 +Name = "a blood orb" +Description = "(This item has 2 charges left)" +Flags = {Take} +Attributes = {Weight=70} + +TypeID = 3252 +Name = "the horn of postman" +Description = "The magical horn will grant you the trustworthy postman rank" +Flags = {Take} +Attributes = {Weight=2300} + +TypeID = 3253 +Name = "a backpack of holding" +Flags = {Container,Take} +Attributes = {Capacity=24,Weight=1500,SlotType=BACKPACK} + +TypeID = 3254 +Name = "a roc feather" +Flags = {Take} +Attributes = {Weight=10} + +TypeID = 3255 +Name = "a drum" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3256 +Name = "a trumpet" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3257 +Name = "a horn" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3258 +Name = "a mandolin" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3259 +Name = "a horn" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3260 +Name = "a lyre" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3261 +Name = "a panpipe" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3262 +Name = "a flute" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3263 +Name = "a gemmed lamp" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3264 +Name = "a sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3500,WeaponType=SWORD,Attack=14,Defense=12} + +TypeID = 3265 +Name = "a two handed sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=7000,SlotType=TWOHANDED,WeaponType=SWORD,Attack=30,Defense=25} + +TypeID = 3266 +Name = "a battle axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5000,SlotType=TWOHANDED,WeaponType=AXE,Attack=25,Defense=10} + +TypeID = 3267 +Name = "a dagger" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=950,WeaponType=SWORD,Attack=8,Defense=6} + +TypeID = 3268 +Name = "a hand axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1800,WeaponType=AXE,Attack=10,Defense=5} + +TypeID = 3269 +Name = "a halberd" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=9000,SlotType=TWOHANDED,WeaponType=AXE,Attack=35,Defense=14} + +TypeID = 3270 +Name = "a club" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2500,WeaponType=CLUB,Attack=7,Defense=7} + +TypeID = 3271 +Name = "a spike sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5000,WeaponType=SWORD,Attack=24,Defense=21} + +TypeID = 3272 +Name = "a rapier" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1500,WeaponType=SWORD,Attack=10,Defense=8} + +TypeID = 3273 +Name = "a sabre" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2500,WeaponType=SWORD,Attack=12,Defense=10} + +TypeID = 3274 +Name = "an axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4000,WeaponType=AXE,Attack=12,Defense=6} + +TypeID = 3275 +Name = "a double axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=7000,SlotType=TWOHANDED,WeaponType=AXE,Attack=35,Defense=12} + +TypeID = 3276 +Name = "a hatchet" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3500,WeaponType=AXE,Attack=15,Defense=8} + +TypeID = 3277 +Name = "a spear" +Flags = {Cumulative,Take,Distance} +Attributes = {Weight=2000,Range=7,Attack=25,Defense=0,MissileEffect=1,Fragility=3} + +TypeID = 3278 +Name = "a magic longsword" +Description = "It's the magic Cyclopmania Sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4300,SlotType=TWOHANDED,WeaponType=SWORD,Attack=55,Defense=40} + +TypeID = 3279 +Name = "a war hammer" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8500,SlotType=TWOHANDED,WeaponType=CLUB,Attack=45,Defense=10} + +TypeID = 3280 +Name = "a fire sword" +Description = "The blade is a magic flame" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2300,Brightness=3,LightColor=199,WeaponType=SWORD,Attack=35,Defense=20} + +TypeID = 3281 +Name = "a giant sword" +Description = "This sword has been forged by ancient giants" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=18000,SlotType=TWOHANDED,WeaponType=SWORD,Attack=46,Defense=22} + +TypeID = 3282 +Name = "a morning star" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5400,WeaponType=CLUB,Attack=25,Defense=11} + +TypeID = 3283 +Name = "a carlin sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4000,WeaponType=SWORD,Attack=15,Defense=13} + +TypeID = 3284 +Name = "an ice rapier" +Description = "A deadly but fragile weapon" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1500,ExpireTarget=0,TotalUses=1,WeaponType=SWORD,Attack=100,Defense=1} + +TypeID = 3285 +Name = "a longsword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4200,WeaponType=SWORD,Attack=17,Defense=14} + +TypeID = 3286 +Name = "a mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3800,WeaponType=CLUB,Attack=16,Defense=11} + +TypeID = 3287 +Name = "a throwing star" +Flags = {MultiUse,Cumulative,Take,Distance} +Attributes = {Weight=200,Range=7,Attack=35,Defense=0,MissileEffect=8,Fragility=10} + +TypeID = 3288 +Name = "a magic sword" +Description = "It's the Sword of Valor" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4200,WeaponType=SWORD,Attack=48,Defense=35} + +TypeID = 3289 +Name = "a staff" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3800,SlotType=TWOHANDED,WeaponType=CLUB,Attack=10,Defense=25} + +TypeID = 3290 +Name = "a silver dagger" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1020,WeaponType=SWORD,Attack=8,Defense=7} + +TypeID = 3291 +Name = "a knife" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=420,WeaponType=SWORD,Attack=7,Defense=5} + +TypeID = 3292 +Name = "a combat knife" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=870,WeaponType=SWORD,Attack=8,Defense=6} + +TypeID = 3293 +Name = "a sickle" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1050,WeaponType=AXE,Attack=7,Defense=4} + +TypeID = 3294 +Name = "a short sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3500,WeaponType=SWORD,Attack=11,Defense=11} + +TypeID = 3295 +Name = "a bright sword" +Description = "The blade shimmers in light blue" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2900,Brightness=2,LightColor=143,WeaponType=SWORD,Attack=36,Defense=30} + +TypeID = 3296 +Name = "a warlord sword" +Description = "Strong powers flow in this magic sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6400,SlotType=TWOHANDED,WeaponType=SWORD,Attack=53,Defense=38} + +TypeID = 3297 +Name = "a serpent sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4100,WeaponType=SWORD,Attack=26,Defense=15} + +TypeID = 3298 +Name = "a throwing knife" +Flags = {MultiUse,Cumulative,Take,Distance} +Attributes = {Weight=500,Range=7,Attack=25,Defense=0,MissileEffect=9,Fragility=7} + +TypeID = 3299 +Name = "a poison dagger" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=880,WeaponType=SWORD,Attack=18,Defense=8} + +TypeID = 3300 +Name = "a katana" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3100,WeaponType=SWORD,Attack=16,Defense=12} + +TypeID = 3301 +Name = "a broadsword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5250,SlotType=TWOHANDED,WeaponType=SWORD,Attack=26,Defense=23} + +TypeID = 3302 +Name = "a dragon lance" +Description = "The extraordinary sharp blade penetrates every armor" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6700,SlotType=TWOHANDED,WeaponType=AXE,Attack=47,Defense=16} + +TypeID = 3303 +Name = "a great axe" +Description = "A masterpiece of a dwarven smith" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=9000,SlotType=TWOHANDED,WeaponType=AXE,Attack=52,Defense=22} + +TypeID = 3304 +Name = "a crowbar" +Flags = {MultiUse,UseEvent,Take,Weapon} +Attributes = {Weight=2100,WeaponType=CLUB,Attack=6,Defense=6} + +TypeID = 3305 +Name = "a battle hammer" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6800,WeaponType=CLUB,Attack=24,Defense=14} + +TypeID = 3306 +Name = "a golden sickle" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1950,WeaponType=AXE,Attack=13,Defense=6} + +TypeID = 3307 +Name = "a scimitar" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2900,WeaponType=SWORD,Attack=19,Defense=13} + +TypeID = 3308 +Name = "a machete" +Flags = {MultiUse,UseEvent,Take,Weapon} +Attributes = {Weight=1650,WeaponType=SWORD,Attack=12,Defense=9} + +TypeID = 3309 +Name = "a thunder hammer" +Description = "It is blessed by the gods of Tibia" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=12500,WeaponType=CLUB,Attack=49,Defense=35} + +TypeID = 3310 +Name = "an iron hammer" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6600,WeaponType=CLUB,Attack=18,Defense=10} + +TypeID = 3311 +Name = "a clerical mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5800,WeaponType=CLUB,Attack=28,Defense=15} + +TypeID = 3312 +Name = "a silver mace" +Description = "You feel an aura of protection" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6700,WeaponType=CLUB,Attack=40,Defense=30} + +TypeID = 3313 +Name = "an obsidian lance" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8000,SlotType=TWOHANDED,WeaponType=AXE,Attack=34,Defense=10} + +TypeID = 3314 +Name = "a naginata" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=7800,SlotType=TWOHANDED,WeaponType=AXE,Attack=39,Defense=25} + +TypeID = 3315 +Name = "a guardian halberd" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=11000,SlotType=TWOHANDED,WeaponType=AXE,Attack=46,Defense=15} + +TypeID = 3316 +Name = "an orcish axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4500,WeaponType=AXE,Attack=23,Defense=12} + +TypeID = 3317 +Name = "a barbarian axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5100,WeaponType=AXE,Attack=28,Defense=18} + +TypeID = 3318 +Name = "a knight axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5900,WeaponType=AXE,Attack=33,Defense=21} + +TypeID = 3319 +Name = "a stonecutter axe" +Description = "You feel the power of this mighty axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=9900,WeaponType=AXE,Attack=50,Defense=30} + +TypeID = 3320 +Name = "a fire axe" +Description = "The blade is a magic flame" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4000,Brightness=3,LightColor=199,WeaponType=AXE,Attack=38,Defense=16} + +TypeID = 3321 +Name = "an enchanted staff" +Description = "Temporal magic powers enchant this staff" +Flags = {MultiUse,Take,Expire,Weapon} +Attributes = {Weight=3800,SlotType=TWOHANDED,ExpireTarget=3289,TotalExpireTime=60,WeaponType=CLUB,Attack=39,Defense=45} + +TypeID = 3322 +Name = "a dragon hammer" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=9700,WeaponType=CLUB,Attack=32,Defense=20} + +TypeID = 3323 +Name = "a dwarven axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8200,WeaponType=AXE,Attack=31,Defense=19} + +TypeID = 3324 +Name = "a skull staff" +Description = "The staff longs for death" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1700,Brightness=2,LightColor=180,WeaponType=CLUB,Attack=36,Defense=12} + +TypeID = 3325 +Name = "a light mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4100,WeaponType=CLUB,Attack=14,Defense=9} + +TypeID = 3326 +Name = "a foil" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1450,WeaponType=SWORD,Attack=9,Defense=11} + +TypeID = 3327 +Name = "a daramanian mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6800,WeaponType=CLUB,Attack=21,Defense=12} + +TypeID = 3328 +Name = "a daramanian waraxe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5250,SlotType=TWOHANDED,WeaponType=AXE,Attack=39,Defense=15} + +TypeID = 3329 +Name = "a daramanian axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4100,WeaponType=AXE,Attack=16,Defense=8} + +TypeID = 3330 +Name = "a heavy machete" +Flags = {MultiUse,UseEvent,Take,Weapon} +Attributes = {Weight=1840,WeaponType=SWORD,Attack=16,Defense=10} + +TypeID = 3331 +Name = "a ravager's axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5250,SlotType=TWOHANDED,WeaponType=AXE,Attack=49,Defense=14} + +TypeID = 3332 +Name = "a hammer of wrath" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=7000,SlotType=TWOHANDED,WeaponType=CLUB,Attack=48,Defense=12} + +TypeID = 3333 +Name = "a crystal mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8000,WeaponType=CLUB,Attack=38,Defense=16} + +TypeID = 3334 +Name = "a pharaoh sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=15000,WeaponType=SWORD,Attack=41,Defense=23} + +TypeID = 3335 +Name = "a twin axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6400,SlotType=TWOHANDED,WeaponType=AXE,Attack=45,Defense=24} + +TypeID = 3336 +Name = "a studded club" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3500,WeaponType=CLUB,Attack=9,Defense=8} + +TypeID = 3337 +Name = "a bone club" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3900,WeaponType=CLUB,Attack=12,Defense=8} + +TypeID = 3338 +Name = "a bone sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1900,WeaponType=SWORD,Attack=14,Defense=10} + +TypeID = 3339 +Name = "a djinn blade" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2450,WeaponType=SWORD,Attack=38,Defense=22} + +TypeID = 3340 +Name = "a heavy mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=11000,SlotType=TWOHANDED,WeaponType=CLUB,Attack=50,Defense=15} + +TypeID = 3341 +Name = "an arcane staff" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4000,WeaponType=CLUB,Attack=50,Defense=30} + +TypeID = 3342 +Name = "a war axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6150,WeaponType=AXE,Attack=20,Defense=10} + +TypeID = 3343 +Name = "a lich staff" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4100,WeaponType=CLUB,Attack=40,Defense=30} + +TypeID = 3344 +Name = "a beastslayer axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6150,WeaponType=AXE,Attack=35,Defense=12} + +TypeID = 3345 +Name = "a templar scytheblade" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2900,WeaponType=SWORD,Attack=23,Defense=15} + +TypeID = 3346 +Name = "a ripper lance" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8000,SlotType=TWOHANDED,WeaponType=AXE,Attack=28,Defense=7} + +TypeID = 3347 +Name = "a hunting spear" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8000,SlotType=TWOHANDED,WeaponType=AXE,Attack=18,Defense=8} + +TypeID = 3348 +Name = "a banana staff" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5000,WeaponType=CLUB,Attack=25,Defense=15} + +TypeID = 3349 +Name = "a crossbow" +Flags = {Take,Distance} +Attributes = {Weight=4000,SlotType=TWOHANDED,Range=7,AmmoType=BOLT} + +TypeID = 3350 +Name = "a bow" +Flags = {Take,Distance} +Attributes = {Weight=3100,SlotType=TWOHANDED,Range=7,AmmoType=ARROW} + +TypeID = 3351 +Name = "a steel helmet" +Flags = {Take,Armor} +Attributes = {Weight=4600,SlotType=HEAD,ArmorValue=6} + +TypeID = 3352 +Name = "a chain helmet" +Flags = {Take,Armor} +Attributes = {Weight=4200,SlotType=HEAD,ArmorValue=2} + +TypeID = 3353 +Name = "an iron helmet" +Flags = {Take,Armor} +Attributes = {Weight=3000,SlotType=HEAD,ArmorValue=5} + +TypeID = 3354 +Name = "a brass helmet" +Flags = {Take,Armor} +Attributes = {Weight=2700,SlotType=HEAD,ArmorValue=3} + +TypeID = 3355 +Name = "a leather helmet" +Flags = {Take,Armor} +Attributes = {Weight=2200,SlotType=HEAD,ArmorValue=1} + +TypeID = 3356 +Name = "a devil helmet" +Flags = {Take,Armor} +Attributes = {Weight=5000,SlotType=HEAD,ArmorValue=7} + +TypeID = 3357 +Name = "a plate armor" +Flags = {Take,Armor} +Attributes = {Weight=12000,SlotType=BODY,ArmorValue=10} + +TypeID = 3358 +Name = "a chain armor" +Flags = {Take,Armor} +Attributes = {Weight=10000,SlotType=BODY,ArmorValue=6} + +TypeID = 3359 +Name = "a brass armor" +Flags = {Take,Armor} +Attributes = {Weight=8000,SlotType=BODY,ArmorValue=8} + +TypeID = 3360 +Name = "a golden armor" +Description = "It's an enchanted armor" +Flags = {Take,Armor} +Attributes = {Weight=8000,SlotType=BODY,ArmorValue=14} + +TypeID = 3361 +Name = "a leather armor" +Flags = {Take,Armor} +Attributes = {Weight=6000,SlotType=BODY,ArmorValue=4} + +TypeID = 3362 +Name = "studded legs" +Flags = {Take,Armor} +Attributes = {Weight=2600,SlotType=LEGS,ArmorValue=2} + +TypeID = 3363 +Name = "dragon scale legs" +Flags = {Take,Armor} +Attributes = {Weight=4800,SlotType=LEGS,ArmorValue=10} + +TypeID = 3364 +Name = "golden legs" +Flags = {Take,Armor} +Attributes = {Weight=5600,SlotType=LEGS,ArmorValue=9} + +TypeID = 3365 +Name = "a golden helmet" +Description = "It's the famous Helmet of the Stars" +Flags = {Take,Armor} +Attributes = {Weight=3200,SlotType=HEAD,ArmorValue=12} + +TypeID = 3366 +Name = "a magic plate armor" +Description = "An enchanted gem glows on the plate armor" +Flags = {Take,Armor} +Attributes = {Weight=8500,SlotType=BODY,ArmorValue=17} + +TypeID = 3367 +Name = "a viking helmet" +Flags = {Take,Armor} +Attributes = {Weight=3900,SlotType=HEAD,ArmorValue=4} + +TypeID = 3368 +Name = "a winged helmet" +Description = "It's the Helmet of Hermes" +Flags = {Take,Armor} +Attributes = {Weight=1200,SlotType=HEAD,ArmorValue=10} + +TypeID = 3369 +Name = "a warrior helmet" +Flags = {Take,Armor} +Attributes = {Weight=6800,SlotType=HEAD,ArmorValue=8} + +TypeID = 3370 +Name = "a knight armor" +Flags = {Take,Armor} +Attributes = {Weight=12000,SlotType=BODY,ArmorValue=12} + +TypeID = 3371 +Name = "knight legs" +Flags = {Take,Armor} +Attributes = {Weight=7000,SlotType=LEGS,ArmorValue=8} + +TypeID = 3372 +Name = "brass legs" +Flags = {Take,Armor} +Attributes = {Weight=3800,SlotType=LEGS,ArmorValue=5} + +TypeID = 3373 +Name = "a strange helmet" +Flags = {Take,Armor} +Attributes = {Weight=4600,SlotType=HEAD,ArmorValue=6} + +TypeID = 3374 +Name = "a legion helmet" +Flags = {Take,Armor} +Attributes = {Weight=3100,SlotType=HEAD,ArmorValue=4} + +TypeID = 3375 +Name = "a soldier helmet" +Flags = {Take,Armor} +Attributes = {Weight=3200,SlotType=HEAD,ArmorValue=5} + +TypeID = 3376 +Name = "a studded helmet" +Flags = {Take,Armor} +Attributes = {Weight=2450,SlotType=HEAD,ArmorValue=2} + +TypeID = 3377 +Name = "a scale armor" +Flags = {Take,Armor} +Attributes = {Weight=10500,SlotType=BODY,ArmorValue=9} + +TypeID = 3378 +Name = "a studded armor" +Flags = {Take,Armor} +Attributes = {Weight=7100,SlotType=BODY,ArmorValue=5} + +TypeID = 3379 +Name = "a doublet" +Flags = {Take,Armor} +Attributes = {Weight=2500,SlotType=BODY,ArmorValue=2} + +TypeID = 3380 +Name = "a noble armor" +Flags = {Take,Armor} +Attributes = {Weight=12000,SlotType=BODY,ArmorValue=11} + +TypeID = 3381 +Name = "a crown armor" +Flags = {Take,Armor} +Attributes = {Weight=9900,SlotType=BODY,ArmorValue=13} + +TypeID = 3382 +Name = "crown legs" +Flags = {Take,Armor} +Attributes = {Weight=6500,SlotType=LEGS,ArmorValue=8} + +TypeID = 3383 +Name = "a dark armor" +Flags = {Take,Armor} +Attributes = {Weight=12000,SlotType=BODY,ArmorValue=10} + +TypeID = 3384 +Name = "a dark helmet" +Flags = {Take,Armor} +Attributes = {Weight=4600,SlotType=HEAD,ArmorValue=6} + +TypeID = 3385 +Name = "a crown helmet" +Flags = {Take,Armor} +Attributes = {Weight=2950,SlotType=HEAD,ArmorValue=7} + +TypeID = 3386 +Name = "a dragon scale mail" +Flags = {Take,Armor} +Attributes = {Weight=11400,SlotType=BODY,ArmorValue=15} + +TypeID = 3387 +Name = "a demon helmet" +Description = "You hear an evil whispering from inside" +Flags = {Take,Armor} +Attributes = {Weight=2950,SlotType=HEAD,ArmorValue=10} + +TypeID = 3388 +Name = "a demon armor" +Flags = {Take,Armor} +Attributes = {Weight=8000,SlotType=BODY,ArmorValue=16} + +TypeID = 3389 +Name = "demon legs" +Flags = {Take,Armor} +Attributes = {Weight=7000,SlotType=LEGS,ArmorValue=9} + +TypeID = 3390 +Name = "a horned helmet" +Flags = {Take,Armor} +Attributes = {Weight=5100,SlotType=HEAD,ArmorValue=11} + +TypeID = 3391 +Name = "a crusader helmet" +Flags = {Take,Armor} +Attributes = {Weight=5200,SlotType=HEAD,ArmorValue=8} + +TypeID = 3392 +Name = "a royal helmet" +Description = "An excellent masterpiece of a smith" +Flags = {Take,Armor} +Attributes = {Weight=4800,SlotType=HEAD,ArmorValue=9} + +TypeID = 3393 +Name = "an amazon helmet" +Flags = {Take,Armor} +Attributes = {Weight=2950,SlotType=HEAD,ArmorValue=7} + +TypeID = 3394 +Name = "an amazon armor" +Flags = {Take,Armor} +Attributes = {Weight=9900,SlotType=BODY,ArmorValue=13} + +TypeID = 3395 +Name = "a ceremonial mask" +Flags = {Take,Armor} +Attributes = {Weight=4000,SlotType=HEAD,Brightness=3,LightColor=215,ArmorValue=9} + +TypeID = 3396 +Name = "a dwarfen helmet" +Flags = {Take,Armor} +Attributes = {Weight=4200,SlotType=HEAD,ArmorValue=6} + +TypeID = 3397 +Name = "a dwarven armor" +Flags = {Take,Armor} +Attributes = {Weight=13000,SlotType=BODY,ArmorValue=10} + +TypeID = 3398 +Name = "dwarfen legs" +Flags = {Take,Armor} +Attributes = {Weight=4900,SlotType=LEGS,ArmorValue=6} + +TypeID = 3399 +Name = "an elven mail" +Flags = {Take,Armor} +Attributes = {Weight=9000,SlotType=BODY,ArmorValue=9} + +TypeID = 3400 +Name = "a dragon scale helmet" +Flags = {Take,Armor} +Attributes = {Weight=3250,SlotType=HEAD,ArmorValue=9} + +TypeID = 3401 +Name = "elven legs" +Flags = {Take,Armor} +Attributes = {Weight=3300,SlotType=LEGS,ArmorValue=4} + +TypeID = 3402 +Name = "a native armor" +Flags = {Take,Armor} +Attributes = {Weight=8000,SlotType=BODY,ArmorValue=7} + +TypeID = 3403 +Name = "a tribal mask" +Flags = {Take,Armor} +Attributes = {Weight=2500,SlotType=HEAD,ArmorValue=2} + +TypeID = 3404 +Name = "a leopard armor" +Flags = {Take,Armor} +Attributes = {Weight=9500,SlotType=BODY,ArmorValue=9} + +TypeID = 3405 +Name = "a horseman helmet" +Flags = {Take,Armor} +Attributes = {Weight=4200,SlotType=HEAD,ArmorValue=6} + +TypeID = 3406 +Name = "a feather headdress" +Flags = {Take,Armor} +Attributes = {Weight=2100,SlotType=HEAD,ArmorValue=2} + +TypeID = 3407 +Name = "a charmer's tiara" +Flags = {Take,Armor} +Attributes = {Weight=2200,SlotType=HEAD,ArmorValue=2} + +TypeID = 3408 +Name = "a beholder helmet" +Flags = {Take,Armor} +Attributes = {Weight=4600,SlotType=HEAD,ArmorValue=7} + +TypeID = 3409 +Name = "a steel shield" +Flags = {Take,Shield} +Attributes = {Weight=6900,Defense=21} + +TypeID = 3410 +Name = "a plate shield" +Flags = {Take,Shield} +Attributes = {Weight=6500,Defense=17} + +TypeID = 3411 +Name = "a brass shield" +Flags = {Take,Shield} +Attributes = {Weight=6000,Defense=16} + +TypeID = 3412 +Name = "a wooden shield" +Flags = {Take,Shield} +Attributes = {Weight=4000,Defense=14} + +TypeID = 3413 +Name = "a battle shield" +Flags = {Take,Shield} +Attributes = {Weight=6200,Defense=23} + +TypeID = 3414 +Name = "a mastermind shield" +Description = "It's an enchanted shield" +Flags = {Take,Shield} +Attributes = {Weight=5700,Defense=37} + +TypeID = 3415 +Name = "a guardian shield" +Flags = {Take,Shield} +Attributes = {Weight=5500,Defense=30} + +TypeID = 3416 +Name = "a dragon shield" +Flags = {Take,Shield} +Attributes = {Weight=6000,Defense=31} + +TypeID = 3417 +Name = "a shield of honour" +Description = "A mighty shield warded by the gods of Tibia" +Flags = {Take,Shield} +Attributes = {Weight=5400,Defense=33} + +TypeID = 3418 +Name = "a beholder shield" +Flags = {Take,Shield} +Attributes = {Weight=4700,Defense=28} + +TypeID = 3419 +Name = "a crown shield" +Flags = {Take,Shield} +Attributes = {Weight=6200,Defense=32} + +TypeID = 3420 +Name = "a demon shield" +Description = "This powerful shield seems to be as light as air" +Flags = {Take,Shield} +Attributes = {Weight=2600,Defense=35} + +TypeID = 3421 +Name = "a dark shield" +Flags = {Take,Shield} +Attributes = {Weight=5200,Defense=25} + +TypeID = 3422 +Name = "a great shield" +Description = "The shield is made of dragon scales" +Flags = {Take,Shield} +Attributes = {Weight=8400,Defense=38} + +TypeID = 3423 +Name = "a blessed shield" +Description = "The shield grants divine protection" +Flags = {Take,Shield} +Attributes = {Weight=6800,Defense=40} + +TypeID = 3424 +Name = "an ornamented shield" +Description = "Many gems sparkle on the shield" +Flags = {Take,Shield} +Attributes = {Weight=6700,Defense=22} + +TypeID = 3425 +Name = "a dwarven shield" +Flags = {Take,Shield} +Attributes = {Weight=5500,Defense=26} + +TypeID = 3426 +Name = "a studded shield" +Flags = {Take,Shield} +Attributes = {Weight=5800,Defense=15} + +TypeID = 3427 +Name = "a rose shield" +Flags = {Take,Shield} +Attributes = {Weight=5200,Defense=27} + +TypeID = 3428 +Name = "a tower shield" +Flags = {Take,Shield} +Attributes = {Weight=8200,Defense=32} + +TypeID = 3429 +Name = "a black shield" +Description = "An unholy creature covers the shield" +Flags = {Take,Shield} +Attributes = {Weight=4200,Defense=18} + +TypeID = 3430 +Name = "a copper shield" +Flags = {Take,Shield} +Attributes = {Weight=6300,Defense=19} + +TypeID = 3431 +Name = "a viking shield" +Flags = {Take,Shield} +Attributes = {Weight=6600,Defense=22} + +TypeID = 3432 +Name = "an ancient shield" +Flags = {Take,Shield} +Attributes = {Weight=6100,Defense=27} + +TypeID = 3433 +Name = "a griffin shield" +Flags = {Take,Shield} +Attributes = {Weight=5000,Defense=29} + +TypeID = 3434 +Name = "a vampire shield" +Description = "Dark powers enchant this shield" +Flags = {Take,Shield} +Attributes = {Weight=3800,Defense=34} + +TypeID = 3435 +Name = "a castle shield" +Flags = {Take,Shield} +Attributes = {Weight=4900,Defense=28} + +TypeID = 3436 +Name = "a medusa shield" +Flags = {Take,Shield} +Attributes = {Weight=5800,Defense=33} + +TypeID = 3437 +Name = "an amazon shield" +Flags = {Take,Shield} +Attributes = {Weight=6200,Defense=32} + +TypeID = 3438 +Name = "an eagle shield" +Flags = {Take,Shield} +Attributes = {Weight=6200,Defense=32} + +TypeID = 3439 +Name = "a phoenix shield" +Description = "This shield feels warm to the touch" +Flags = {Take,Shield} +Attributes = {Weight=3500,Defense=34} + +TypeID = 3440 +Name = "a scarab shield" +Flags = {Take,Shield} +Attributes = {Weight=4700,Defense=25} + +TypeID = 3441 +Name = "a bone shield" +Flags = {Take,Shield} +Attributes = {Weight=5500,Defense=20} + +TypeID = 3442 +Name = "a tempest shield" +Flags = {Take,Shield} +Attributes = {Weight=5100,Defense=36} + +TypeID = 3443 +Name = "a tusk shield" +Flags = {Take,Shield} +Attributes = {Weight=6900,Defense=27} + +TypeID = 3444 +Name = "a sentinel shield" +Flags = {Take,Shield} +Attributes = {Weight=4900,Defense=22} + +TypeID = 3445 +Name = "a salamander shield" +Flags = {Take,Shield} +Attributes = {Weight=5900,Defense=26} + +TypeID = 3446 +Name = "a bolt" +Flags = {Cumulative,Take,Ammo} +Attributes = {Weight=80,AmmoType=BOLT,Attack=30,MissileEffect=2,Fragility=100} + +TypeID = 3447 +Name = "an arrow" +Flags = {Cumulative,Take,Ammo} +Attributes = {Weight=70,AmmoType=ARROW,Attack=25,MissileEffect=3,Fragility=100} + +TypeID = 3448 +Name = "a poison arrow" +Flags = {Cumulative,Take,Ammo} +Attributes = {Weight=80,AmmoType=ARROW,Attack=10,MissileEffect=6,Fragility=100,WeaponSpecialEffect=1,AttackStrength=50} + +TypeID = 3449 +Name = "a burst arrow" +Flags = {Cumulative,Take,Ammo} +Attributes = {Weight=90,AmmoType=ARROW,Attack=0,MissileEffect=7,Fragility=100,WeaponSpecialEffect=2,AttackStrength=30} + +TypeID = 3450 +Name = "a power bolt" +Flags = {Cumulative,Take,Ammo} +Attributes = {Weight=90,AmmoType=BOLT,Attack=40,MissileEffect=14,Fragility=100} + +TypeID = 3451 +Name = "a pitchfork" +Flags = {MultiUse,Take} +Attributes = {Weight=2500} + +TypeID = 3452 +Name = "a rake" +Flags = {MultiUse,Take} +Attributes = {Weight=1500} + +TypeID = 3453 +Name = "a scythe" +Flags = {UseEvent,MultiUse,Take,Weapon} +Attributes = {Weight=3000,SlotType=TWOHANDED,WeaponType=CLUB,Attack=8,Defense=3} + +TypeID = 3454 +Name = "a broom" +Flags = {MultiUse,Take} +Attributes = {Weight=1100} + +TypeID = 3455 +Name = "a hoe" +Flags = {MultiUse,Take} +Attributes = {Weight=2800} + +TypeID = 3456 +Name = "a pick" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=4500} + +TypeID = 3457 +Name = "a shovel" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=3500} + +TypeID = 3458 +Name = "an anvil" +Flags = {Unpass,Unmove,Height} + +TypeID = 3459 +Name = "a wooden hammer" +Flags = {MultiUse,Take} +Attributes = {Weight=600} + +TypeID = 3460 +Name = "a hammer" +Flags = {MultiUse,Take} +Attributes = {Weight=1150} + +TypeID = 3461 +Name = "a saw" +Flags = {MultiUse,Take} +Attributes = {Weight=1000} + +TypeID = 3462 +Name = "a small axe" +Flags = {MultiUse,Take} +Attributes = {Weight=2000} + +TypeID = 3463 +Name = "a mirror" +Flags = {MultiUse,Take} +Attributes = {Weight=950} + +TypeID = 3464 +Name = "a baking tray" +Flags = {Take} +Attributes = {Weight=1200} + +TypeID = 3465 +Name = "a pot" +Flags = {MultiUse,FluidContainer,Unpass,Take,Destroy,Height} +Attributes = {Weight=5250,DestroyTarget=3142} + +TypeID = 3466 +Name = "a pan" +Flags = {Take} +Attributes = {Weight=1800} + +TypeID = 3467 +Name = "a fork" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 3468 +Name = "a spoon" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 3469 +Name = "a knife" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=100} + +TypeID = 3470 +Name = "a wooden spoon" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 3471 +Name = "a cleaver" +Flags = {Take} +Attributes = {Weight=660} + +TypeID = 3472 +Name = "an oven spatula" +Flags = {Take} +Attributes = {Weight=1400} + +TypeID = 3473 +Name = "a rolling pin" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3474 +Name = "a bowel" +Flags = {Take} +Attributes = {Weight=1850} + +TypeID = 3475 +Name = "a bowel" +Flags = {Take} +Attributes = {Weight=1850} + +TypeID = 3476 +Name = "a bowel" +Flags = {Take} +Attributes = {Weight=1850} + +TypeID = 3477 +Name = "a ewer" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=1750} + +TypeID = 3478 +Name = "a ewer" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=1750} + +TypeID = 3479 +Name = "a ewer" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=1750} + +TypeID = 3480 +Name = "a ewer" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=1750} + +TypeID = 3481 +Name = "a closed trap" +Flags = {UseEvent,Take} +Attributes = {Weight=2100} + +TypeID = 3482 +Name = "a trap" +Flags = {UseEvent,CollisionEvent,Take} +Attributes = {Weight=2100} + +TypeID = 3483 +Name = "a fishing rod" +Flags = {MultiUse,DistUse,UseEvent,Take} +Attributes = {Weight=850} + +TypeID = 3484 +Name = "a telescope" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=3487,DestroyTarget=3137} + +TypeID = 3485 +Name = "a telescope" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=3486,DestroyTarget=3137} + +TypeID = 3486 +Name = "a telescope" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=3484,DestroyTarget=3137} + +TypeID = 3487 +Name = "a telescope" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=3485,DestroyTarget=3137} + +TypeID = 3488 +Name = "a ships telescope" +Flags = {Unpass,Unmove,Unlay,Rotate,Height} +Attributes = {RotateTarget=3491} + +TypeID = 3489 +Name = "a ships telescope" +Flags = {Unpass,Unmove,Unlay,Rotate,Height} +Attributes = {RotateTarget=3490} + +TypeID = 3490 +Name = "a ships telescope" +Flags = {Unpass,Unmove,Unlay,Rotate,Height} +Attributes = {RotateTarget=3488} + +TypeID = 3491 +Name = "a ships telescope" +Flags = {Unpass,Unmove,Unlay,Rotate,Height} +Attributes = {RotateTarget=3489} + +TypeID = 3492 +Name = "a worm" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3493 +Name = "a crane" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3494 +Name = "a crane" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3495 +Name = "a crane" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3496 +Name = "a crane" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3497 +Name = "a locker" +Flags = {Container,Unpass,Unmove,Height,Depot} +Attributes = {Capacity=30} + +TypeID = 3498 +Name = "a locker" +Flags = {Container,Unpass,Unmove,Height,Depot} +Attributes = {Capacity=30} + +TypeID = 3499 +Name = "a locker" +Flags = {Container,Unpass,Unmove,Height,Depot} +Attributes = {Capacity=30} + +TypeID = 3500 +Name = "a locker" +Flags = {Container,Unpass,Unmove,Height,Depot} +Attributes = {Capacity=30} + +TypeID = 3501 +Name = "a mailbox" +Description = "Royal Tibia Mail" +Flags = {Bottom,CollisionEvent,Unpass,Unmove,Height,Mailbox} + +TypeID = 3502 +Name = "a depot chest" +Flags = {Container,Unmove} +Attributes = {Capacity=30} + +TypeID = 3503 +Name = "a parcel" +Flags = {Container,Avoid,Take,Height} +Attributes = {Capacity=10,Weight=1800} + +TypeID = 3504 +Name = "a stamped parcel" +Flags = {Container,Avoid,Take,Height} +Attributes = {Capacity=10,Weight=1800} + +TypeID = 3505 +Name = "a letter" +Flags = {Text,Write,Take} +Attributes = {MaxLength=2000,Weight=50} + +TypeID = 3506 +Name = "a stamped letter" +Flags = {Text,Take} +Attributes = {Weight=50} + +TypeID = 3507 +Name = "a label" +Flags = {Text,Write,Take} +Attributes = {MaxLength=80,Weight=10} + +TypeID = 3508 +Name = "a mailbox" +Description = "Royal Tibia Mail" +Flags = {Bottom,CollisionEvent,Unpass,Unmove,Height,Mailbox} + +TypeID = 3509 +Name = "an inkwell" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 3510 +Name = "a coal basin" +Flags = {Unpass,Destroy,Height} +Attributes = {DestroyTarget=3143,Brightness=8,LightColor=207} + +TypeID = 3511 +Name = "a coal basin" +Flags = {Unpass,Destroy,Height} +Attributes = {DestroyTarget=3143,Brightness=8,LightColor=206} + +TypeID = 3512 +Name = "a coal basin" +Flags = {Unpass,Destroy,Height} +Attributes = {DestroyTarget=3143,Brightness=8,LightColor=206} + +TypeID = 3513 +Name = "a coal basin" +Flags = {Unpass,Destroy,Height} +Attributes = {DestroyTarget=3143,Brightness=8,LightColor=206} + +TypeID = 3514 +Name = "an empty coal basin" +Flags = {Unpass,Destroy,Height} +Attributes = {DestroyTarget=3143,Brightness=0,LightColor=215} + +TypeID = 3515 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3516 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3517 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3518 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3519 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,CollisionEvent,Unmove,Height} + +TypeID = 3520 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,CollisionEvent,Unmove,Height} + +TypeID = 3521 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3522 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3523 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3524 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3525 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3526 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3527 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3528 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3529 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3530 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3531 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3532 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3533 +Name = "a black token" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 3534 +Name = "a white token" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 3535 +Name = "a white pawn" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3536 +Name = "a white castle" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3537 +Name = "a white knight" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3538 +Name = "a white bishop" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3539 +Name = "the white queen" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3540 +Name = "the white king" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3541 +Name = "a black pawn" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3542 +Name = "a black castle" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3543 +Name = "a black knight" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3544 +Name = "a black bishop" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3545 +Name = "the black queen" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3546 +Name = "the black king" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3547 +Name = "a tic-tac-toe token" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 3548 +Name = "a tic-tac-toe token" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 3549 +Name = "soft boots" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=800,DeEquipTarget=6529,SlotType=FEET,ExpireTarget=6530,TotalExpireTime=14400,HealthGain=1,HealthTicks=2000,ManaGain=2,ManaTicks=1000} + +TypeID = 3550 +Name = "patched boots" +Flags = {Take,Armor} +Attributes = {Weight=1000,SlotType=FEET,ArmorValue=2} + +TypeID = 3551 +Name = "sandals" +Flags = {Take} +Attributes = {Weight=600,SlotType=FEET} + +TypeID = 3552 +Name = "leather boots" +Flags = {Take,Armor} +Attributes = {Weight=900,SlotType=FEET,ArmorValue=1} + +TypeID = 3553 +Name = "bunnyslippers" +Flags = {Take} +Attributes = {Weight=600,SlotType=FEET} + +TypeID = 3554 +Name = "steel boots" +Flags = {Take,Armor} +Attributes = {Weight=2900,SlotType=FEET,ArmorValue=3} + +TypeID = 3555 +Name = "golden boots" +Flags = {Take,Armor} +Attributes = {Weight=3100,SlotType=FEET,ArmorValue=4} + +TypeID = 3556 +Name = "crocodile boots" +Flags = {Take,Armor} +Attributes = {Weight=900,SlotType=FEET,ArmorValue=1} + +TypeID = 3557 +Name = "plate legs" +Flags = {Take,Armor} +Attributes = {Weight=5000,SlotType=LEGS,ArmorValue=7} + +TypeID = 3558 +Name = "chain legs" +Flags = {Take,Armor} +Attributes = {Weight=3500,SlotType=LEGS,ArmorValue=3} + +TypeID = 3559 +Name = "leather legs" +Flags = {Take,Armor} +Attributes = {Weight=1800,SlotType=LEGS,ArmorValue=1} + +TypeID = 3560 +Name = "a bast skirt" +Flags = {Take} +Attributes = {Weight=350,SlotType=LEGS} + +TypeID = 3561 +Name = "a jacket" +Flags = {Take,Armor} +Attributes = {Weight=2400,SlotType=BODY,ArmorValue=1} + +TypeID = 3562 +Name = "a coat" +Flags = {Take,Armor} +Attributes = {Weight=2700,SlotType=BODY,ArmorValue=1} + +TypeID = 3563 +Name = "a green tunic" +Flags = {Take,Armor} +Attributes = {Weight=930,SlotType=BODY,ArmorValue=1} + +TypeID = 3564 +Name = "a red tunic" +Flags = {Take,Armor} +Attributes = {Weight=1400,SlotType=BODY,ArmorValue=2} + +TypeID = 3565 +Name = "a cape" +Flags = {Take,Armor} +Attributes = {Weight=3200,SlotType=BODY,ArmorValue=1} + +TypeID = 3566 +Name = "a red robe" +Description = "The robe is artfully embroidered" +Flags = {Take,Armor} +Attributes = {Weight=2600,SlotType=BODY,ArmorValue=1} + +TypeID = 3567 +Name = "a blue robe" +Description = "It is a magic robe" +Flags = {Take,Armor} +Attributes = {Weight=2200,SlotType=BODY,ArmorValue=11} + +TypeID = 3568 +Name = "a simple dress" +Flags = {Take} +Attributes = {Weight=2400,SlotType=BODY} + +TypeID = 3569 +Name = "a white dress" +Flags = {Take} +Attributes = {Weight=2400,SlotType=BODY} + +TypeID = 3570 +Name = "a ball gown" +Flags = {Take} +Attributes = {Weight=2500,SlotType=BODY} + +TypeID = 3571 +Name = "a rangers cloak" +Flags = {Take,Armor} +Attributes = {Weight=3200,SlotType=BODY,ArmorValue=1} + +TypeID = 3572 +Name = "a scarf" +Flags = {Take,Armor} +Attributes = {Weight=200,SlotType=NECKLACE,ArmorValue=1} + +TypeID = 3573 +Name = "a magician hat" +Flags = {Take,Armor} +Attributes = {Weight=750,SlotType=HEAD,ArmorValue=1} + +TypeID = 3574 +Name = "a mystic turban" +Description = "Something is strange about this turban" +Flags = {Take,Armor} +Attributes = {Weight=850,SlotType=HEAD,ArmorValue=1} + +TypeID = 3575 +Name = "a wood cape" +Flags = {Take,Armor} +Attributes = {Weight=1100,SlotType=HEAD,ArmorValue=2} + +TypeID = 3576 +Name = "a post officers hat" +Description = "This hat is the insignia of all tibian post officers" +Flags = {Take,Armor} +Attributes = {Weight=700,SlotType=HEAD,ArmorValue=1} + +TypeID = 3577 +Name = "meat" +Flags = {Cumulative,Take} +Attributes = {Nutrition=15,Weight=1300} + +TypeID = 3578 +Name = "a fish" +Flags = {Cumulative,Take} +Attributes = {Nutrition=12,Weight=520} + +TypeID = 3579 +Name = "salmon" +Flags = {Cumulative,Take} +Attributes = {Nutrition=10,Weight=320} + +TypeID = 3580 +Name = "a fish" +Flags = {Cumulative,Take} +Attributes = {Nutrition=17,Weight=830} + +TypeID = 3581 +Name = "shrimp" +Flags = {Cumulative,Take} +Attributes = {Nutrition=4,Weight=50} + +TypeID = 3582 +Name = "ham" +Flags = {Cumulative,Take} +Attributes = {Nutrition=30,Weight=2000} + +TypeID = 3583 +Name = "dragon ham" +Description = "It still contains a small part of the power of a dragon" +Flags = {Cumulative,Take} +Attributes = {Nutrition=60,Weight=3000} + +TypeID = 3584 +Name = "a pear" +Flags = {Cumulative,Take} +Attributes = {Nutrition=5,Weight=140} + +TypeID = 3585 +Name = "a red apple" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=150} + +TypeID = 3586 +Name = "an orange" +Flags = {Cumulative,Take} +Attributes = {Nutrition=13,Weight=110} + +TypeID = 3587 +Name = "a banana" +Flags = {Cumulative,Take} +Attributes = {Nutrition=8,Weight=180} + +TypeID = 3588 +Name = "a blueberry" +Flags = {Cumulative,Take} +Attributes = {Nutrition=1,Weight=20} + +TypeID = 3589 +Name = "a coconut" +Flags = {Cumulative,Take} +Attributes = {Nutrition=18,Weight=480} + +TypeID = 3590 +Name = "a cherry" +Flags = {Cumulative,Take} +Attributes = {Nutrition=1,Weight=20} + +TypeID = 3591 +Name = "a strawberry" +Flags = {Cumulative,Take} +Attributes = {Nutrition=2,Weight=20} + +TypeID = 3592 +Name = "grapes" +Flags = {Take} +Attributes = {Nutrition=9,Weight=250} + +TypeID = 3593 +Name = "a melon" +Flags = {Take} +Attributes = {Nutrition=20,Weight=950} + +TypeID = 3594 +Name = "a pumpkin" +Flags = {Take} +Attributes = {Nutrition=17,Weight=1350} + +TypeID = 3595 +Name = "a carrot" +Flags = {Cumulative,Take} +Attributes = {Nutrition=8,Weight=160} + +TypeID = 3596 +Name = "a tomato" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=100} + +TypeID = 3597 +Name = "a corncob" +Flags = {Cumulative,Take} +Attributes = {Nutrition=9,Weight=350} + +TypeID = 3598 +Name = "a cookie" +Flags = {Cumulative,Take} +Attributes = {Nutrition=2,Weight=10} + +TypeID = 3599 +Name = "a candy cane" +Flags = {Cumulative,Take} +Attributes = {Nutrition=2,Weight=50} + +TypeID = 3600 +Name = "a bread" +Flags = {Cumulative,Take} +Attributes = {Nutrition=10,Weight=500} + +TypeID = 3601 +Name = "a roll" +Flags = {Cumulative,Take} +Attributes = {Nutrition=3,Weight=100} + +TypeID = 3602 +Name = "a brown bread" +Flags = {Cumulative,Take} +Attributes = {Nutrition=8,Weight=400} + +TypeID = 3603 +Name = "flour" +Flags = {UseEvent,Cumulative,MultiUse,Take} +Attributes = {Weight=500} + +TypeID = 3604 +Name = "a lump of dough" +Flags = {UseEvent,Cumulative,MultiUse,Take} +Attributes = {Weight=500} + +TypeID = 3605 +Name = "a bunch of wheat" +Flags = {UseEvent,Cumulative,MultiUse,Take} +Attributes = {Weight=1250} + +TypeID = 3606 +Name = "an egg" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=30} + +TypeID = 3607 +Name = "cheese" +Flags = {Take} +Attributes = {Nutrition=9,Weight=400} + +TypeID = 3608 +Name = "a snowy dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3609 +Name = "a snowy fir tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3610 +Name = "a plum tree" +Flags = {Bottom,Container,Unpass,Unmove,Unlay,Disguise} +Attributes = {Capacity=4,DisguiseTarget=3617} + +TypeID = 3611 +Name = "a firtree" +Flags = {Bottom,Container,Unpass,Unmove,Unlay,Disguise} +Attributes = {Capacity=4,DisguiseTarget=3614} + +TypeID = 3612 +Name = "a dead tree" +Flags = {Bottom,Container,Unpass,Unmove,Unlay,Disguise} +Attributes = {Capacity=4,DisguiseTarget=3634} + +TypeID = 3613 +Name = "a holy tree" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=6,LightColor=143} + +TypeID = 3614 +Name = "a fir tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3615 +Name = "a sycamore" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3616 +Name = "a willow" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3617 +Name = "a plum tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3618 +Name = "a red maple" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3619 +Name = "a pear tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3620 +Name = "a yellow maple" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3621 +Name = "a beech" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3622 +Name = "a poplar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3623 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3624 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3625 +Name = "a dwarf tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3626 +Name = "a pine" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3627 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3628 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3629 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3630 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3631 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3632 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3633 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3634 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3635 +Name = "old rush wood" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3636 +Name = "an old tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3637 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3638 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3639 +Name = "a palm" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3640 +Name = "a coconut palm" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3641 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3642 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3643 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3644 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3645 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3646 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3647 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3648 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3649 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3650 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3651 +Name = "wheat" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=3652,TotalExpireTime=43200} + +TypeID = 3652 +Name = "wheat" +Description = "It's not mature yet" +Flags = {Unmove,Avoid,Expire} +Attributes = {ExpireTarget=3653,TotalExpireTime=43200} + +TypeID = 3653 +Name = "wheat" +Flags = {UseEvent,Unpass,Unmove} + +TypeID = 3654 +Name = "moon flowers" +Flags = {Unmove} + +TypeID = 3655 +Name = "a moon flower" +Flags = {Take} +Attributes = {Weight=10} + +TypeID = 3656 +Name = "a white flower" +Flags = {Unmove} + +TypeID = 3657 +Name = "a heaven blossom" +Flags = {Unmove} + +TypeID = 3658 +Name = "a red rose" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3659 +Name = "a blue rose" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3660 +Name = "a yellow rose" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3661 +Name = "a grave flower" +Flags = {Take} +Attributes = {Weight=60} + +TypeID = 3662 +Name = "a love flower" +Flags = {Unmove} + +TypeID = 3663 +Name = "a royal blossom" +Flags = {Unmove} + +TypeID = 3664 +Name = "a royal blossom" +Flags = {Unmove} + +TypeID = 3665 +Name = "a royal blossom" +Flags = {Unmove} + +TypeID = 3666 +Name = "some sunflowers" +Flags = {Unmove,Avoid} + +TypeID = 3667 +Name = "a sunflower" +Flags = {Unmove} + +TypeID = 3668 +Name = "a tulip" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 3669 +Name = "a water lily" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3670 +Name = "a water lily" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3671 +Name = "a water lily" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3672 +Name = "a water lily" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3673 +Name = "an orange star" +Flags = {Take} +Attributes = {Weight=70} + +TypeID = 3674 +Name = "a goat grass" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3675 +Name = "an orchid" +Flags = {Unmove} + +TypeID = 3676 +Name = "a rosebush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3677 +Name = "a rosebush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3678 +Name = "a rosebush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3679 +Name = "a rosebush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3680 +Name = "a rosebush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3681 +Name = "a bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3682 +Name = "a small fir tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3683 +Name = "a shadow plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3684 +Name = "a branch" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3685 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3686 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3687 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3688 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3689 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3690 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3691 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3692 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3693 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3694 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3695 +Name = "jungle grass" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=3696,TotalExpireTime=300} + +TypeID = 3696 +Name = "jungle grass" +Flags = {UseEvent,Unpass,Unmove} + +TypeID = 3697 +Name = "an agave" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3698 +Name = "a dry bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3699 +Name = "a blueberry bush" +Flags = {UseEvent,Bottom,Unpass,Unmove,Unlay} + +TypeID = 3700 +Name = "a blueberry bush" +Flags = {Bottom,Unpass,Unmove,Unlay,Expire} +Attributes = {ExpireTarget=3699,TotalExpireTime=3600} + +TypeID = 3701 +Name = "jungle grass" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=3702,TotalExpireTime=300} + +TypeID = 3702 +Name = "jungle grass" +Flags = {UseEvent,Unpass,Unmove} + +TypeID = 3703 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3704 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3705 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3706 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3707 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3708 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3709 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3710 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3711 +Name = "a thorn bush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3712 +Name = "a thorn bush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3713 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3714 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3715 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3716 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3717 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3718 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3719 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3720 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3721 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3722 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3723 +Name = "a white mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=9,Weight=40} + +TypeID = 3724 +Name = "a red mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=4,Weight=50} + +TypeID = 3725 +Name = "a brown mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=22,Weight=20} + +TypeID = 3726 +Name = "an orange mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=30,Weight=30} + +TypeID = 3727 +Name = "a wood mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=9,Weight=30} + +TypeID = 3728 +Name = "a dark mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=10} + +TypeID = 3729 +Name = "some mushrooms" +Flags = {Cumulative,Take} +Attributes = {Nutrition=12,Weight=10} + +TypeID = 3730 +Name = "some mushrooms" +Flags = {Cumulative,Take} +Attributes = {Nutrition=3,Weight=10} + +TypeID = 3731 +Name = "a fire mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=36,Weight=10} + +TypeID = 3732 +Name = "a green mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=5,Weight=10} + +TypeID = 3733 +Name = "dark mushrooms" +Flags = {Unmove,Hang} + +TypeID = 3734 +Name = "a blood herb" +Flags = {Cumulative,Take} +Attributes = {Weight=120} + +TypeID = 3735 +Name = "a stone herb" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3736 +Name = "a star herb" +Flags = {Cumulative,Take} +Attributes = {Weight=30} + +TypeID = 3737 +Name = "a fern" +Flags = {Cumulative,Take} +Attributes = {Weight=30} + +TypeID = 3738 +Name = "a sling herb" +Flags = {Cumulative,Take} +Attributes = {Weight=90} + +TypeID = 3739 +Name = "a powder herb" +Flags = {Cumulative,Take} +Attributes = {Weight=50} + +TypeID = 3740 +Name = "a shadow herb" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3741 +Name = "a troll green" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 3742 +Name = "an orange tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3743 +Name = "a thread tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3744 +Name = "a jungle dweller bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3745 +Name = "a tower fern" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3746 +Name = "a snake nest bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3747 +Name = "a green wig bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3748 +Name = "a lizards tongue bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3749 +Name = "a jungle crown plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3750 +Name = "a green fountain bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3751 +Name = "a big fern" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3752 +Name = "a dragons nest tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3753 +Name = "a purple kiss bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3754 +Name = "a small fern" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3755 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3756 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3757 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3758 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3759 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3760 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3761 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3762 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3763 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3764 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3765 +Name = "a bamboo plant" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3766 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3767 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3768 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3769 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3770 +Name = "a bamboo plant" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3771 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3772 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3773 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3774 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3775 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3776 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3777 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3778 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3779 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3780 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3781 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3782 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3783 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3784 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3785 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3786 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3787 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3788 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3789 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3790 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3791 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3792 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3793 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3794 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3795 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3796 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3797 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3798 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3799 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3800 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3801 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3802 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3803 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3804 +Name = "jungle vines" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3805 +Name = "jungle vines" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3806 +Name = "jungle vines" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3807 +Name = "jungle vines" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3808 +Name = "jungle vines" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3809 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3810 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3811 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3812 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3813 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3814 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3815 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3816 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3817 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3818 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3819 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3820 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3821 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3822 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3823 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3824 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3825 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3826 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3827 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3828 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3829 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3830 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3831 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3832 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3833 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3834 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3835 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3836 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3837 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3838 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3839 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3840 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3841 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3842 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3843 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3844 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3845 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3846 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3847 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3848 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3849 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3850 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3851 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3852 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3853 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3854 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3855 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3856 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3857 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3858 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3859 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3860 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3861 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3862 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3863 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3864 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3865 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3866 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3867 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3868 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3869 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3870 +Name = "a chill nettle" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3871 +Name = "a monkey tail" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3872 +Name = "a fairy queen" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3873 +Name = "a crane plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3874 +Name = "a jungle bells plant" +Flags = {Bottom,Unmove} + +TypeID = 3875 +Name = "a dawn singer" +Flags = {Bottom,Unmove} + +TypeID = 3876 +Name = "a turtle sprouter" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3877 +Name = "a bees ballroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3878 +Name = "a giant jungle rose" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3879 +Name = "a jungle rose" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3880 +Name = "a titans orchid" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3881 +Name = "a titans orchid" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3882 +Name = "a titans orchid" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3883 +Name = "a titans orchid" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3884 +Name = "a purple cardinal" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3885 +Name = "a witches cauldron plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3886 +Name = "a gold blossom" +Flags = {Bottom,Unmove} + +TypeID = 3887 +Name = "a gold blossom" +Flags = {Bottom,Unmove} + +TypeID = 3888 +Name = "a meadow star" +Flags = {Bottom,Unmove} + +TypeID = 3889 +Name = "a meadow star" +Flags = {Bottom,Unmove} + +TypeID = 3890 +Name = "a meadow star" +Flags = {Bottom,Unmove} + +TypeID = 3891 +Name = "a sneeze blossom" +Flags = {Bottom,Unmove} + +TypeID = 3892 +Name = "a dew kisser flower" +Flags = {Bottom,Unmove} + +TypeID = 3893 +Name = "a dew kisser flower" +Flags = {Bottom,Unmove} + +TypeID = 3894 +Name = "a dew kisser flower" +Flags = {Bottom,Unmove} + +TypeID = 3895 +Name = "a velvet petal" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3896 +Name = "a velvet petal" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3897 +Name = "a velvet petal" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3898 +Name = "a velvet petal" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3899 +Name = "a devil's tongue flower" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3900 +Name = "a small pearl flower" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3901 +Name = "a large pearl flower" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3902 +Name = "a large pearl flower" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3903 +Name = "a dead man's saddle" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3904 +Name = "a dead man's saddle" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3905 +Name = "a dead man's saddle" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3906 +Name = "a dead man's saddle" +Flags = {Unmove} + +TypeID = 3907 +Name = "dead man's saddles" +Flags = {Unmove} + +TypeID = 3908 +Name = "a moss cap mushroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3909 +Name = "a moss cap mushroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3910 +Name = "a slime table mushroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3911 +Name = "a slime table mushroom" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 3912 +Name = "a slime table mushroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3913 +Name = "slime table mushrooms" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3914 +Name = "a giggle mushroom" +Flags = {Unmove} + +TypeID = 3915 +Name = "giggle mushrooms" +Flags = {Unmove} + +TypeID = 3916 +Name = "a cat's food mushroom" +Flags = {Unmove} + +TypeID = 3917 +Name = "cat's food mushrooms" +Flags = {Unmove} + +TypeID = 3918 +Name = "a glimmer cap mushroom" +Flags = {Unmove} + +TypeID = 3919 +Name = "glimmer cap mushrooms" +Flags = {Unmove} + +TypeID = 3920 +Name = "a giant glimmer cap mushroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3921 +Name = "a large pearl flower" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3922 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3923 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3924 +Name = "a fallen tree" +Flags = {Bottom,Unmove} + +TypeID = 3925 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3926 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3927 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3928 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3929 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3930 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3931 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3932 +Name = "a fallen tree" +Flags = {Bottom,Unmove} + +TypeID = 3933 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3934 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3935 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3936 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3937 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3938 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3939 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3940 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3941 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3942 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3943 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3944 +Name = "a jungle maw" +Flags = {Bottom,CollisionEvent,Unmove} + +TypeID = 3945 +Name = "a jungle maw" +Flags = {Bottom,Unmove,Expire} +Attributes = {ExpireTarget=3944,TotalExpireTime=150} + +TypeID = 3946 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3947 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3948 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3949 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3950 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3951 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3952 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3953 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3954 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3955 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3956 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3957 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3958 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3959 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3960 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3961 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3962 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3963 +Name = "a giant tree root" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3964 +Name = "a giant tree root" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3965 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3966 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3967 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3968 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3969 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3970 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3971 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3972 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3973 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3974 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3975 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3976 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3977 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3978 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3979 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3980 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3981 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3982 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3983 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3984 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3985 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3986 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3987 +Name = "a dead troll" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,Weight=60000,ExpireTarget=3991,TotalExpireTime=1800} + +TypeID = 3988 +Name = "a dead spider" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=4600,ExpireTarget=4003,TotalExpireTime=1200} + +TypeID = 3989 +Name = "a dead cyclops" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4092,TotalExpireTime=1800} + +TypeID = 3990 +Name = "a slain skeleton" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=60000,ExpireTarget=4103,TotalExpireTime=1200} + +TypeID = 3991 +Name = "a dead troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=50000,ExpireTarget=3992,TotalExpireTime=1800} + +TypeID = 3992 +Name = "a dead troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=30000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 3993 +Name = "a dead troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=10000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 3994 +Name = "a dead rat" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300,ExpireTarget=3995,TotalExpireTime=1200} + +TypeID = 3995 +Name = "a dead rat" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4400,ExpireTarget=3996,TotalExpireTime=1200} + +TypeID = 3996 +Name = "a dead rat" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 3997 +Name = "a dead rat" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 3998 +Name = "a dead snake" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1200,ExpireTarget=3999,TotalExpireTime=1200} + +TypeID = 3999 +Name = "a dead snake" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=840,ExpireTarget=4000,TotalExpireTime=1200} + +TypeID = 4000 +Name = "a dead snake" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=620,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4001 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=82000,ExpireTarget=4002,TotalExpireTime=1800} + +TypeID = 4002 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=65000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4003 +Name = "a dead spider" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=4004,TotalExpireTime=1200} + +TypeID = 4004 +Name = "a dead spider" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1200,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4005 +Name = "a dead rotworm" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4006,TotalExpireTime=1200} + +TypeID = 4006 +Name = "a dead rotworm" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4145,TotalExpireTime=1200} + +TypeID = 4007 +Name = "a dead wolf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=26000,ExpireTarget=4008,TotalExpireTime=1800} + +TypeID = 4008 +Name = "a dead wolf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=20000,ExpireTarget=4009,TotalExpireTime=1800} + +TypeID = 4009 +Name = "a dead wolf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=13000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4010 +Name = "a dead wolf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=6000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4011 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=130000,ExpireTarget=4012,TotalExpireTime=1800} + +TypeID = 4012 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=105000,ExpireTarget=4013,TotalExpireTime=1800} + +TypeID = 4013 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=85000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4014 +Name = "a dead minotaur" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4015 +Name = "a dead minotaur" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4016 +Name = "a dead deer" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=68000,ExpireTarget=4017,TotalExpireTime=1800} + +TypeID = 4017 +Name = "a dead deer" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=58000,ExpireTarget=4018,TotalExpireTime=1800} + +TypeID = 4018 +Name = "a dead deer" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=40000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4019 +Name = "a dead deer" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=10000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4020 +Name = "a dead dog" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=21000,ExpireTarget=4021,TotalExpireTime=1200} + +TypeID = 4021 +Name = "a dead dog" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=14000,ExpireTarget=4022,TotalExpireTime=1200} + +TypeID = 4022 +Name = "a dead dog" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=9000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4023 +Name = "a dead dog" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4024 +Name = "a slain skeleton" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=40000,ExpireTarget=4156,TotalExpireTime=1200} + +TypeID = 4025 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4026,TotalExpireTime=1800} + +TypeID = 4026 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4027,TotalExpireTime=1800} + +TypeID = 4027 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4028 +Name = "a dead dragon" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4029 +Name = "a dead spider" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=9000,ExpireTarget=4066,TotalExpireTime=1200} + +TypeID = 4030 +Name = "a dead bear" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4031,TotalExpireTime=1800} + +TypeID = 4031 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4032,TotalExpireTime=1800} + +TypeID = 4032 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4033 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4034 +Name = "a slain ghoul" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=64000,ExpireTarget=4035,TotalExpireTime=1800} + +TypeID = 4035 +Name = "a slain ghoul" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=57000,ExpireTarget=4036,TotalExpireTime=1800} + +TypeID = 4036 +Name = "a slain ghoul" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=39000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4037 +Name = "a slain ghoul" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4038 +Name = "a dead giant spider" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=SLIME,ExpireTarget=4039,TotalExpireTime=1800} + +TypeID = 4039 +Name = "a dead giant spider" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=6,ExpireTarget=4040,TotalExpireTime=1800} + +TypeID = 4040 +Name = "a dead giant spider" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4041 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=90000,ExpireTarget=4042,TotalExpireTime=1800} + +TypeID = 4042 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=70000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4043 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=90000,ExpireTarget=4044,TotalExpireTime=1800} + +TypeID = 4044 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=70000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4045 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=90000,ExpireTarget=4046,TotalExpireTime=1800} + +TypeID = 4046 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=70000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4047 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=140000,ExpireTarget=4048,TotalExpireTime=1800} + +TypeID = 4048 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=110000,ExpireTarget=4013,TotalExpireTime=1800} + +TypeID = 4049 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=2,Weight=80000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4050 +Name = "a dead minotaur" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4051 +Name = "a pile of bones" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4052 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=140000,ExpireTarget=4053,TotalExpireTime=1800} + +TypeID = 4053 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=110000,ExpireTarget=4013,TotalExpireTime=1800} + +TypeID = 4054 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=2,Weight=80000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4055 +Name = "a dead minotaur" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4056 +Name = "a pile of bones" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4057 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=150000,ExpireTarget=4058,TotalExpireTime=1800} + +TypeID = 4058 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=110000,ExpireTarget=4013,TotalExpireTime=1800} + +TypeID = 4059 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=2,Weight=80000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4060 +Name = "a dead minotaur" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4061 +Name = "a pile of bones" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4062 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4063,TotalExpireTime=1800} + +TypeID = 4063 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4027,TotalExpireTime=1800} + +TypeID = 4064 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4065 +Name = "a dead dragon" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4066 +Name = "a dead spider" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=7000,ExpireTarget=4125,TotalExpireTime=1200} + +TypeID = 4067 +Name = "a dead fire devil" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=80000,ExpireTarget=4068,TotalExpireTime=1800} + +TypeID = 4068 +Name = "a dead fire devil" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=60000,ExpireTarget=4069,TotalExpireTime=1800} + +TypeID = 4069 +Name = "a dead fire devil" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4070 +Name = "a dead lion" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4071,TotalExpireTime=1800} + +TypeID = 4071 +Name = "a dead lion" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4072,TotalExpireTime=1800} + +TypeID = 4072 +Name = "a dead lion" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4073 +Name = "a dead lion" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4074 +Name = "a dead bear" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4075,TotalExpireTime=1800} + +TypeID = 4075 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4076,TotalExpireTime=1800} + +TypeID = 4076 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4077 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4078 +Name = "a dead scorpion" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=9000,ExpireTarget=4079,TotalExpireTime=1200} + +TypeID = 4079 +Name = "a dead scorpion" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=6000,ExpireTarget=4146,TotalExpireTime=1200} + +TypeID = 4080 +Name = "a dead wasp" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=9000,ExpireTarget=4081,TotalExpireTime=1200} + +TypeID = 4081 +Name = "a dead wasp" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=6000,ExpireTarget=4082,TotalExpireTime=1200} + +TypeID = 4082 +Name = "a dead wasp" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4083 +Name = "a dead bug" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=10000,ExpireTarget=4084,TotalExpireTime=1200} + +TypeID = 4084 +Name = "a dead bug" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=6500,ExpireTarget=4085,TotalExpireTime=1200} + +TypeID = 4085 +Name = "a dead bug" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4086 +Name = "a dead sheep" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=70000,ExpireTarget=4087,TotalExpireTime=1800} + +TypeID = 4087 +Name = "a dead sheep" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=60000,ExpireTarget=4088,TotalExpireTime=1800} + +TypeID = 4088 +Name = "a dead sheep" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=35000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4089 +Name = "a dead beholder" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=SLIME,ExpireTarget=4090,TotalExpireTime=1800} + +TypeID = 4090 +Name = "a dead beholder" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=SLIME,ExpireTarget=4091,TotalExpireTime=1800} + +TypeID = 4091 +Name = "a dead beholder" +Flags = {Corpse,Expire} +Attributes = {FluidSource=SLIME,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4092 +Name = "a dead cyclops" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4093,TotalExpireTime=1800} + +TypeID = 4093 +Name = "a dead cyclops" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4094 +Name = "remains of a ghost" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=2200,ExpireTarget=4158,TotalExpireTime=1200} + +TypeID = 4095 +Name = "a dead sheep" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=70000,ExpireTarget=4096,TotalExpireTime=1800} + +TypeID = 4096 +Name = "a dead sheep" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=60000,ExpireTarget=4088,TotalExpireTime=1800} + +TypeID = 4097 +Name = "a slain demon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4098,TotalExpireTime=1800} + +TypeID = 4098 +Name = "a slain demon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4099,TotalExpireTime=1800} + +TypeID = 4099 +Name = "a slain demon" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4100 +Name = "a slain demon" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4101 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=90000,ExpireTarget=4102,TotalExpireTime=1800} + +TypeID = 4102 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=70000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4103 +Name = "a slain skeleton" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=4104,TotalExpireTime=1200} + +TypeID = 4104 +Name = "a slain skeleton" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=12000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4105 +Name = "a dead wolf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=45000,ExpireTarget=4106,TotalExpireTime=1800} + +TypeID = 4106 +Name = "a dead wolf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=32000,ExpireTarget=4009,TotalExpireTime=1800} + +TypeID = 4107 +Name = "a dead wolf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=18000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4108 +Name = "a dead wolf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=14000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4109 +Name = "a dead troll" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=92000,ExpireTarget=4110,TotalExpireTime=1800} + +TypeID = 4110 +Name = "a dead troll" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=83000,ExpireTarget=3992,TotalExpireTime=1800} + +TypeID = 4111 +Name = "a dead troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=69000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4112 +Name = "a dead behemoth" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4113,TotalExpireTime=1800} + +TypeID = 4113 +Name = "a dead behemoth" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4114,TotalExpireTime=1800} + +TypeID = 4114 +Name = "a dead behemoth" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4115 +Name = "a dead behemoth" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4116 +Name = "a dead pig" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=75000,ExpireTarget=4117,TotalExpireTime=1800} + +TypeID = 4117 +Name = "a dead pig" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=60000,ExpireTarget=4118,TotalExpireTime=1800} + +TypeID = 4118 +Name = "a dead pig" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=28000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4119 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=110000,ExpireTarget=4120,TotalExpireTime=1800} + +TypeID = 4120 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=90000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4121 +Name = "a dead goblin" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=68000,ExpireTarget=4122,TotalExpireTime=1800} + +TypeID = 4122 +Name = "a dead goblin" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=58000,ExpireTarget=4123,TotalExpireTime=1800} + +TypeID = 4123 +Name = "a dead goblin" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=37000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4124 +Name = "a dead golin" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=28000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4125 +Name = "a dead spider" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4126 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=71000,ExpireTarget=4127,TotalExpireTime=1800} + +TypeID = 4127 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=65000,ExpireTarget=4128,TotalExpireTime=1800} + +TypeID = 4128 +Name = "a dead elf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=39000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4129 +Name = "a dead elf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=27000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4130 +Name = "remains of a mummy" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=48000,ExpireTarget=4131,TotalExpireTime=1200} + +TypeID = 4131 +Name = "remains of a mummy" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=31000,ExpireTarget=4132,TotalExpireTime=1200} + +TypeID = 4132 +Name = "remains of a mummy" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=12000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4133 +Name = "a split stone golem" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4134,TotalExpireTime=1800} + +TypeID = 4134 +Name = "a split stone golem" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4135,TotalExpireTime=1800} + +TypeID = 4135 +Name = "a split stone golem" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4136 +Name = "a split stone golem" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4137 +Name = "a slain vampire" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=78000,ExpireTarget=4138,TotalExpireTime=1200} + +TypeID = 4138 +Name = "a slain vampire" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=69000,ExpireTarget=4139,TotalExpireTime=1200} + +TypeID = 4139 +Name = "a slain vampire" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=48000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4140 +Name = "a slain vampire" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=33000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4141 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=80000,ExpireTarget=4142,TotalExpireTime=1800} + +TypeID = 4142 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=71000,ExpireTarget=4143,TotalExpireTime=1800} + +TypeID = 4143 +Name = "a dead dwarf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=52000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4144 +Name = "a dead dwarf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=34000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4145 +Name = "a dead rotworm" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4146 +Name = "a dead scorpion" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2500,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4147 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=51000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4148 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=110000,ExpireTarget=4149,TotalExpireTime=1800} + +TypeID = 4149 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=92000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4150 +Name = "a dead war wolf" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4151,TotalExpireTime=1800} + +TypeID = 4151 +Name = "a dead war wolf" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4152,TotalExpireTime=1800} + +TypeID = 4152 +Name = "a dead war wolf" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4153 +Name = "a dead orc and wolf" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4154,TotalExpireTime=1800} + +TypeID = 4154 +Name = "a dead orc and wolf" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4155,TotalExpireTime=1800} + +TypeID = 4155 +Name = "a dead orc and wolf" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4156 +Name = "a slain skeleton" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=26000,ExpireTarget=4157,TotalExpireTime=1200} + +TypeID = 4157 +Name = "a slain skeleton" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=14000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4158 +Name = "remains of a ghost" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=1300,ExpireTarget=4159,TotalExpireTime=1200} + +TypeID = 4159 +Name = "remains of a ghost" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=900,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4160 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=71000,ExpireTarget=4161,TotalExpireTime=1800} + +TypeID = 4161 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=65000,ExpireTarget=4128,TotalExpireTime=1800} + +TypeID = 4162 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=71000,ExpireTarget=4163,TotalExpireTime=1800} + +TypeID = 4163 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=65000,ExpireTarget=4128,TotalExpireTime=1800} + +TypeID = 4164 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=80000,ExpireTarget=4165,TotalExpireTime=1800} + +TypeID = 4165 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=71000,ExpireTarget=4143,TotalExpireTime=1800} + +TypeID = 4166 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=80000,ExpireTarget=4167,TotalExpireTime=1800} + +TypeID = 4167 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=71000,ExpireTarget=4143,TotalExpireTime=1800} + +TypeID = 4168 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=80000,ExpireTarget=4169,TotalExpireTime=1800} + +TypeID = 4169 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=71000,ExpireTarget=4143,TotalExpireTime=1800} + +TypeID = 4170 +Name = "a dead djinn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4171,TotalExpireTime=1800} + +TypeID = 4171 +Name = "a dead djinn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4172,TotalExpireTime=1800} + +TypeID = 4172 +Name = "a dead djinn" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4173 +Name = "a dead rabbit" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=5400,ExpireTarget=4174,TotalExpireTime=1200} + +TypeID = 4174 +Name = "a dead rabbit" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3200,ExpireTarget=4175,TotalExpireTime=1200} + +TypeID = 4175 +Name = "a dead rabbit" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4176 +Name = "a dead swamp troll" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=SLIME,Weight=60000,ExpireTarget=4177,TotalExpireTime=1800} + +TypeID = 4177 +Name = "a dead swamp troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=50000,ExpireTarget=4178,TotalExpireTime=1800} + +TypeID = 4178 +Name = "a dead swamp troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=30000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4179 +Name = "a slain banshee" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=7,Weight=11000,ExpireTarget=4180,TotalExpireTime=1800} + +TypeID = 4180 +Name = "a slain banshee" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=8000,ExpireTarget=4181,TotalExpireTime=1800} + +TypeID = 4181 +Name = "a slain banshee" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4182 +Name = "a dead djinn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4183,TotalExpireTime=1800} + +TypeID = 4183 +Name = "a dead djinn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4184,TotalExpireTime=1800} + +TypeID = 4184 +Name = "a dead djinn" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4185 +Name = "a dead scarab" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=SLIME,ExpireTarget=4186,TotalExpireTime=1800} + +TypeID = 4186 +Name = "a dead scarab" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=6,ExpireTarget=4187,TotalExpireTime=1800} + +TypeID = 4187 +Name = "a dead scarab" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4188 +Name = "a dead cobra" +Flags = {Take,Corpse,Expire} +Attributes = {FluidSource=BLOOD,Weight=1320,ExpireTarget=4189,TotalExpireTime=1200} + +TypeID = 4189 +Name = "a dead cobra" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=920,ExpireTarget=4190,TotalExpireTime=1200} + +TypeID = 4190 +Name = "a dead cobra" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=680,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4191 +Name = "a dead larva" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=1050,ExpireTarget=4192,TotalExpireTime=1200} + +TypeID = 4192 +Name = "a dead larva" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=820,ExpireTarget=4193,TotalExpireTime=1200} + +TypeID = 4193 +Name = "a dead larva" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=530,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4194 +Name = "a dead scarab" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=12000,ExpireTarget=4195,TotalExpireTime=1200} + +TypeID = 4195 +Name = "a dead scarab" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=7800,ExpireTarget=4196,TotalExpireTime=1200} + +TypeID = 4196 +Name = "a dead scarab" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3600,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4197 +Name = "a dead pharaoh" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4198,TotalExpireTime=1800} + +TypeID = 4198 +Name = "a dead pharaoh" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4199,TotalExpireTime=1800} + +TypeID = 4199 +Name = "a dead pharaoh" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4200 +Name = "a dead hyaena" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,Weight=31000,ExpireTarget=4201,TotalExpireTime=1800} + +TypeID = 4201 +Name = "a dead hyaena" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=4202,TotalExpireTime=1800} + +TypeID = 4202 +Name = "a dead hyaena" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=8000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4203 +Name = "a dead gargoyle" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4204,TotalExpireTime=1800} + +TypeID = 4204 +Name = "a dead gargoyle" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4205,TotalExpireTime=1800} + +TypeID = 4205 +Name = "a dead gargoyle" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4206 +Name = "a slain lich" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4207,TotalExpireTime=1800} + +TypeID = 4207 +Name = "a slain lich" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4208,TotalExpireTime=1800} + +TypeID = 4208 +Name = "a slain lich" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4209 +Name = "a slain crypt shambler" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,Weight=31000,ExpireTarget=4210,TotalExpireTime=1800} + +TypeID = 4210 +Name = "a slain crypt shambler" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=4211,TotalExpireTime=1800} + +TypeID = 4211 +Name = "a slain crypt shambler" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=8000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4212 +Name = "a slain bonebeast" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4213,TotalExpireTime=1800} + +TypeID = 4213 +Name = "a slain bonebeast" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4214,TotalExpireTime=1800} + +TypeID = 4214 +Name = "a slain bonebeast" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4215 +Name = "a dead pharaoh" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4216,TotalExpireTime=1800} + +TypeID = 4216 +Name = "a dead pharaoh" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4217,TotalExpireTime=1800} + +TypeID = 4217 +Name = "a dead pharaoh" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4218 +Name = "a dead efreet" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4219,TotalExpireTime=1800} + +TypeID = 4219 +Name = "a dead efreet" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4220,TotalExpireTime=1800} + +TypeID = 4220 +Name = "a dead efreet" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4221 +Name = "a dead marid" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4222,TotalExpireTime=1800} + +TypeID = 4222 +Name = "a dead marid" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4223,TotalExpireTime=1800} + +TypeID = 4223 +Name = "a dead marid" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4224 +Name = "a dead badger" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=5900,ExpireTarget=4225,TotalExpireTime=1800} + +TypeID = 4225 +Name = "a dead badger" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=5500,ExpireTarget=4226,TotalExpireTime=1800} + +TypeID = 4226 +Name = "a dead badger" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3500,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4227 +Name = "a dead skunk" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=5600,ExpireTarget=4228,TotalExpireTime=1800} + +TypeID = 4228 +Name = "a dead skunk" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=4229,TotalExpireTime=1800} + +TypeID = 4229 +Name = "a dead skunk" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2500,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4230 +Name = "a dead gazer" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=5400,ExpireTarget=4231,TotalExpireTime=1800} + +TypeID = 4231 +Name = "a dead gazer" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3200,ExpireTarget=4232,TotalExpireTime=1800} + +TypeID = 4232 +Name = "a dead gazer" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4233 +Name = "a dead elder beholder" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4234,TotalExpireTime=1800} + +TypeID = 4234 +Name = "a dead elder beholder" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4235,TotalExpireTime=1800} + +TypeID = 4235 +Name = "a dead elder beholder" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4236 +Name = "a dead yeti" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4237,TotalExpireTime=1800} + +TypeID = 4237 +Name = "a dead yeti" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4238,TotalExpireTime=1800} + +TypeID = 4238 +Name = "a dead yeti" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4239 +Name = "a dead yeti" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4240 +Name = "a dead human" +Flags = {Container,Corpse,Expire,AllowDistRead} +Attributes = {Capacity=10,ExpireTarget=4241,TotalExpireTime=1800} + +TypeID = 4241 +Name = "a dead human" +Flags = {Container,Corpse,Expire,AllowDistRead} +Attributes = {Capacity=10,ExpireTarget=4242,TotalExpireTime=1800} + +TypeID = 4242 +Name = "a dead human" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=50000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4243 +Name = "a dead human" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4244 +Name = "a dead human" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4245 +Name = "a dead human" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4246 +Name = "a dead human" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4247 +Name = "a dead human" +Flags = {Container,Corpse,Expire,AllowDistRead} +Attributes = {Capacity=10,ExpireTarget=4248,TotalExpireTime=1800} + +TypeID = 4248 +Name = "a dead human" +Flags = {Container,Corpse,Expire,AllowDistRead} +Attributes = {Capacity=10,ExpireTarget=4242,TotalExpireTime=1800} + +TypeID = 4249 +Name = "a dead troll" +Flags = {Container,Take} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=80000} + +TypeID = 4250 +Name = "a dead spider" +Flags = {Container,Take} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=10000} + +TypeID = 4251 +Name = "a dead cyclops" +Flags = {Container} +Attributes = {Capacity=12,FluidSource=BLOOD} + +TypeID = 4252 +Name = "a dead troll" +Flags = {Take} +Attributes = {Weight=60000} + +TypeID = 4253 +Name = "a dead troll" +Flags = {Take} +Attributes = {Weight=30000} + +TypeID = 4254 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=10000} + +TypeID = 4255 +Name = "a dead rat" +Flags = {Container,Take} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300} + +TypeID = 4256 +Name = "a dead rat" +Flags = {Take} +Attributes = {Weight=4400} + +TypeID = 4257 +Name = "a dead rat" +Flags = {Take} +Attributes = {Weight=3000} + +TypeID = 4258 +Name = "a dead rat" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4259 +Name = "a dead snake" +Flags = {Take} +Attributes = {Weight=1000} + +TypeID = 4260 +Name = "a dead snake" +Flags = {Take} +Attributes = {Weight=650} + +TypeID = 4261 +Name = "a dead snake" +Flags = {Take} +Attributes = {Weight=300} + +TypeID = 4262 +Name = "a dead orc" +Flags = {Container,Take} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=90000} + +TypeID = 4263 +Name = "a dead orc" +Flags = {Take} +Attributes = {Weight=60000} + +TypeID = 4264 +Name = "a dead spider" +Flags = {Take} +Attributes = {Weight=6000} + +TypeID = 4265 +Name = "a dead spider" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4266 +Name = "a dead rotworm" +Flags = {Container} +Attributes = {Capacity=10} + +TypeID = 4267 +Name = "a dead rotworm" + +TypeID = 4268 +Name = "a dead wolf" +Flags = {Container,Take} +Attributes = {Capacity=6,FluidSource=BLOOD,Weight=21000} + +TypeID = 4269 +Name = "a dead wolf" +Flags = {Container,Take} +Attributes = {Capacity=4,Weight=15000} + +TypeID = 4270 +Name = "a dead wolf" +Flags = {Take} +Attributes = {Weight=10000} + +TypeID = 4271 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=6000} + +TypeID = 4272 +Name = "a dead minotaur" +Flags = {Container,Take} +Attributes = {Capacity=12,FluidSource=BLOOD,Weight=150000} + +TypeID = 4273 +Name = "a dead minotaur" +Flags = {Container,Take} +Attributes = {Capacity=7,Weight=110000} + +TypeID = 4274 +Name = "a dead minotaur" +Flags = {Container,Take} +Attributes = {Capacity=2,Weight=80000} + +TypeID = 4275 +Name = "a dead minotaur" +Flags = {Take} +Attributes = {Weight=40000} + +TypeID = 4276 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=20000} + +TypeID = 4277 +Name = "a dead deer" +Flags = {Container,Take} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=60000} + +TypeID = 4278 +Name = "a dead deer" +Flags = {Container,Take} +Attributes = {Capacity=3,Weight=50000} + +TypeID = 4279 +Name = "a dead deer" +Flags = {Container,Take} +Attributes = {Capacity=1,Weight=30000} + +TypeID = 4280 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=10000} + +TypeID = 4281 +Name = "a dead dog" +Flags = {Container,Take} +Attributes = {Capacity=2,FluidSource=BLOOD,Weight=20000} + +TypeID = 4282 +Name = "a dead dog" +Flags = {Container,Take} +Attributes = {Capacity=1,Weight=10000} + +TypeID = 4283 +Name = "a dead dog" +Flags = {Take} +Attributes = {Weight=5000} + +TypeID = 4284 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=1000} + +TypeID = 4285 +Name = "a pile of bones" +Flags = {Container,Take} +Attributes = {Capacity=10,Weight=10000} + +TypeID = 4286 +Name = "a dead dragon" +Flags = {Container} +Attributes = {Capacity=16,FluidSource=BLOOD} + +TypeID = 4287 +Name = "a dead dragon" +Flags = {Container} +Attributes = {Capacity=10} + +TypeID = 4288 +Name = "a dead dragon" +Flags = {Container} +Attributes = {Capacity=5} + +TypeID = 4289 +Name = "a pile of bones" + +TypeID = 4290 +Name = "remains of a ghost" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=2200} + +TypeID = 4291 +Name = "a dead bear" +Flags = {Container} +Attributes = {Capacity=8,FluidSource=BLOOD} + +TypeID = 4292 +Name = "a dead bear" + +TypeID = 4293 +Name = "a dead bear" + +TypeID = 4294 +Name = "a pile of bones" + +TypeID = 4295 +Name = "a slain ghoul" +Flags = {Container,Take} +Attributes = {Capacity=4,Weight=40000} + +TypeID = 4296 +Name = "a slain ghoul" +Flags = {Take} +Attributes = {Weight=30000} + +TypeID = 4297 +Name = "a slain ghoul" +Flags = {Take} +Attributes = {Weight=15000} + +TypeID = 4298 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=5000} + +TypeID = 4299 +Name = "a dead cyclops" + +TypeID = 4300 +Name = "a pile of bones" + +TypeID = 4301 +Name = "a dead rabbit" +Flags = {Container,Take} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300} + +TypeID = 4302 +Name = "a dead rabbit" +Flags = {Take} +Attributes = {Weight=4400} + +TypeID = 4303 +Name = "a dead rabbit" +Flags = {Take} +Attributes = {Weight=3000} + +TypeID = 4304 +Name = "a pile of bones" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4305 +Name = "a pile of bones" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4306 +Name = "a pile of bones" +Flags = {Unmove} + +TypeID = 4307 +Name = "a pile of bones" +Flags = {Unmove} + +TypeID = 4308 +Name = "a pile of bones" +Flags = {Unmove} + +TypeID = 4309 +Name = "a pile of bones" +Flags = {Unmove} + +TypeID = 4310 +Name = "a dead elephant" +Flags = {Container} +Attributes = {Capacity=15} + +TypeID = 4311 +Name = "a dead human" +Flags = {Container,Corpse} +Attributes = {Capacity=10} + +TypeID = 4312 +Name = "a dead human" +Flags = {Container} +Attributes = {Capacity=5} + +TypeID = 4313 +Name = "a corpse" +Flags = {Take} +Attributes = {Weight=50000} + +TypeID = 4314 +Name = "a corpse" +Flags = {Take} +Attributes = {Weight=30000} + +TypeID = 4315 +Name = "a skeleton" +Flags = {Take} +Attributes = {Weight=15000} + +TypeID = 4316 +Name = "a skeleton" +Flags = {Take} +Attributes = {Weight=10000} + +TypeID = 4317 +Name = "some bones" +Flags = {Take} +Attributes = {Weight=5000} + +TypeID = 4318 +Name = "a dead crab" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300,ExpireTarget=4319,TotalExpireTime=1200} + +TypeID = 4319 +Name = "a dead crab" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4400,ExpireTarget=4320,TotalExpireTime=1200} + +TypeID = 4320 +Name = "a dead crab" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4321 +Name = "a dead lizard templar" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4322,TotalExpireTime=1200} + +TypeID = 4322 +Name = "a dead lizard templar" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4323,TotalExpireTime=1200} + +TypeID = 4323 +Name = "a dead lizard templar" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4324 +Name = "a dead lizard sentinel" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4325,TotalExpireTime=1200} + +TypeID = 4325 +Name = "a dead lizard sentinel" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4326,TotalExpireTime=1200} + +TypeID = 4326 +Name = "a dead lizard sentinel" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4327 +Name = "a dead lizard snakecharmer" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4328,TotalExpireTime=1200} + +TypeID = 4328 +Name = "a dead lizard snakecharmer" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4329,TotalExpireTime=1200} + +TypeID = 4329 +Name = "a dead lizard snakecharmer" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4330 +Name = "a dead chicken" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300,ExpireTarget=4331,TotalExpireTime=1200} + +TypeID = 4331 +Name = "a dead chicken" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4400,ExpireTarget=4332,TotalExpireTime=1200} + +TypeID = 4332 +Name = "a dead chicken" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4333 +Name = "a dead kongra" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4334,TotalExpireTime=1200} + +TypeID = 4334 +Name = "a dead kongra" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4335,TotalExpireTime=1200} + +TypeID = 4335 +Name = "a dead kongra" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4336 +Name = "a dead merlkin" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4337,TotalExpireTime=1200} + +TypeID = 4337 +Name = "a dead merlkin" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4338,TotalExpireTime=1200} + +TypeID = 4338 +Name = "a dead merlkin" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4339 +Name = "a dead sibang" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4340,TotalExpireTime=1200} + +TypeID = 4340 +Name = "a dead sibang" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4341,TotalExpireTime=1200} + +TypeID = 4341 +Name = "a dead sibang" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4342 +Name = "a dead crocodile" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4343,TotalExpireTime=1200} + +TypeID = 4343 +Name = "a dead crocodile" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4344,TotalExpireTime=1200} + +TypeID = 4344 +Name = "a dead crocodile" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4345 +Name = "a dead carniphila" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4346,TotalExpireTime=1200} + +TypeID = 4346 +Name = "a dead carniphila" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4347,TotalExpireTime=1200} + +TypeID = 4347 +Name = "a dead carniphila" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4348 +Name = "a dead hydra" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4349,TotalExpireTime=1800} + +TypeID = 4349 +Name = "a dead hydra" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4350,TotalExpireTime=1800} + +TypeID = 4350 +Name = "a dead hydra" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4351 +Name = "a dead panda" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4352,TotalExpireTime=1200} + +TypeID = 4352 +Name = "a dead panda" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4353,TotalExpireTime=1200} + +TypeID = 4353 +Name = "a dead panda" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4354 +Name = "a dead centipede" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4355,TotalExpireTime=1200} + +TypeID = 4355 +Name = "a dead centipede" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4356,TotalExpireTime=1200} + +TypeID = 4356 +Name = "a dead centipede" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4357 +Name = "a dead tiger" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4358,TotalExpireTime=1200} + +TypeID = 4358 +Name = "a dead tiger" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4359,TotalExpireTime=1200} + +TypeID = 4359 +Name = "a dead tiger" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4360 +Name = "a dead elephant" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4361,TotalExpireTime=1800} + +TypeID = 4361 +Name = "a dead elephant" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4362,TotalExpireTime=1800} + +TypeID = 4362 +Name = "a dead elephant" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4363 +Name = "a dead bat" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300,ExpireTarget=4364,TotalExpireTime=1200} + +TypeID = 4364 +Name = "a dead bat" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4400,ExpireTarget=4365,TotalExpireTime=1200} + +TypeID = 4365 +Name = "a dead bat" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4366 +Name = "a dead flamingo" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4367,TotalExpireTime=1200} + +TypeID = 4367 +Name = "a dead flamingo" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4368,TotalExpireTime=1200} + +TypeID = 4368 +Name = "a dead flamingo" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4369 +Name = "a dead dworc" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4370,TotalExpireTime=1200} + +TypeID = 4370 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4371,TotalExpireTime=1200} + +TypeID = 4371 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4372 +Name = "a dead dworc" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4373,TotalExpireTime=1200} + +TypeID = 4373 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4374,TotalExpireTime=1200} + +TypeID = 4374 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4375 +Name = "a dead dworc" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4376,TotalExpireTime=1200} + +TypeID = 4376 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4377,TotalExpireTime=1200} + +TypeID = 4377 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4378 +Name = "a dead butterfly" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4379 +Name = "a dead parrot" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300,ExpireTarget=4380,TotalExpireTime=1200} + +TypeID = 4380 +Name = "a dead parrot" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4400,ExpireTarget=4381,TotalExpireTime=1200} + +TypeID = 4381 +Name = "a dead parrot" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4382 +Name = "a dead bird" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4383,TotalExpireTime=1200} + +TypeID = 4383 +Name = "a dead bird" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4384,TotalExpireTime=1200} + +TypeID = 4384 +Name = "a dead bird" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4385 +Name = "a dead tarantula" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4386,TotalExpireTime=1200} + +TypeID = 4386 +Name = "a dead tarantula" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4387,TotalExpireTime=1200} + +TypeID = 4387 +Name = "a dead tarantula" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4388 +Name = "a dead serpent spawn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4389,TotalExpireTime=1800} + +TypeID = 4389 +Name = "a dead serpent spawn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4390,TotalExpireTime=1800} + +TypeID = 4390 +Name = "a dead serpent spawn" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4391 +Name = "a lifeless nettle" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4392,TotalExpireTime=1200} + +TypeID = 4392 +Name = "a lifeless nettle" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4393 +Name = "a drawbridge" +Flags = {Bank,Unmove,Avoid,Disguise} +Attributes = {Waypoints=90,DisguiseTarget=1771} + +TypeID = 4394 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4395 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4396 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4397 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4398 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4399 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4400 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4401 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4402 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4403 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4404 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4405 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4406 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4407 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4408 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4409 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4410 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4411 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4412 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4413 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4414 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4415 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4416 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4417 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4418 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4419 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4420 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4421 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4422 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4423 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4424 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4425 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4426 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4427 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4428 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4429 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4430 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4431 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4432 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4433 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4434 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4435 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4436 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4437 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4438 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4439 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4440 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4441 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4442 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4443 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4444 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4445 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4446 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4447 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4448 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4449 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4450 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4451 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4452 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4453 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4454 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4455 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4456 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4457 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4458 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4459 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4460 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4461 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4462 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4463 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4464 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4465 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4466 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4467 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4468 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4469 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4470 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4471 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4472 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4473 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4474 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4475 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4476 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4477 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4478 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4479 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4480 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4481 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4482 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4483 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4484 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4485 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4486 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4487 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4488 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4489 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4490 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4491 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4492 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4493 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4494 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4495 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4496 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4497 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4498 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4499 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4500 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4501 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4502 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4503 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4504 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4505 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4506 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4507 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4508 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4509 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4510 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4511 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4512 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4513 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4514 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4515 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4516 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4517 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4518 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4519 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4520 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4521 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4522 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4523 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4524 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4525 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4526 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4527 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4528 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4529 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4530 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4531 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4532 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4533 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4534 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4535 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4536 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4537 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4538 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4539 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4540 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4541 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4542 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4543 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4544 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4545 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4546 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4547 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4548 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4549 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4550 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4551 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4552 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4553 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4554 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4555 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4556 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4557 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4558 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4559 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4560 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4561 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4562 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4563 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4564 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4565 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4566 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4567 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4568 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4569 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4570 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4571 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4572 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4573 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4574 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4575 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4576 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4577 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4578 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4579 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4580 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4581 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4582 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4583 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4584 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4585 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4586 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4587 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4588 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4589 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4590 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4591 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4592 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4593 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4594 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4595 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4596 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4597 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4598 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4599 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4600 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4601 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4602 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4603 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4604 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4605 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4606 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4607 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4608 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4609 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4597,TotalExpireTime=2200} + +TypeID = 4610 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4598,TotalExpireTime=2200} + +TypeID = 4611 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4599,TotalExpireTime=2200} + +TypeID = 4612 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4600,TotalExpireTime=2200} + +TypeID = 4613 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4601,TotalExpireTime=2200} + +TypeID = 4614 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4602,TotalExpireTime=2200} + +TypeID = 4615 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4609} + +TypeID = 4616 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4610} + +TypeID = 4617 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4611} + +TypeID = 4618 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4612} + +TypeID = 4619 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4613} + +TypeID = 4620 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4614} + +TypeID = 4621 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4622 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4623 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4624 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4625 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4626 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4627 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4628 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4629 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4630 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4631 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4632 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4633 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4634 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4635 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4636 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4637 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4638 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4639 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4640 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4641 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4642 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4643 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4644 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4645 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4646 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4647 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4648 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4649 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4650 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4651 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4652 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4653 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=1,FluidSource=WATER} + +TypeID = 4654 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=1,FluidSource=WATER} + +TypeID = 4655 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=1,FluidSource=WATER} + +TypeID = 4656 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4657 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4658 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4659 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4660 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4661 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4662 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4663 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4664 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4665 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4666 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4667 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4668 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4669 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4670 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4671 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4672 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4673 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4674 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4675 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4676 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4677 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4678 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4679 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4680 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4681 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4682 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4683 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4684 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4685 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4686 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4687 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4688 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4689 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4690 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4691 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4692 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4693 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4694 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4695 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4696 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4697 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4698 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4699 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4700 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4701 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4702 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4703 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4704 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4705 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4706 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4707 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4708 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4709 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4710 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4711 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4712 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4713 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4714 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4715 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4716 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4717 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4718 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4719 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4720 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4721 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4722 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4723 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4724 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4725 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4726 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4727 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4728 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4729 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4730 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4731 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4732 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4733 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4734 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4735 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4736 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4737 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4738 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4739 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4740 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4741 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4742 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4743 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4744 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4745 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4746 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4747 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4748 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4749 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4750 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4751 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4752 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4753 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4754 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4755 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4756 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4757 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4758 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4759 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4760 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4761 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4762 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4763 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4764 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4765 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4766 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4767 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4768 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4769 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4770 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4771 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4772 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4773 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4774 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4775 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4776 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4777 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4778 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4779 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4780 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4781 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4782 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4783 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4784 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4785 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4786 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4787 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4788 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4789 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4790 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4791 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4792 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4793 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4794 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4795 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4796 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4797 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4798 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4799 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4800 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4801 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4802 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4803 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4804 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4805 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4806 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4807 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4808 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4809 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4810 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4811 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4812 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4813 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4814 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4815 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4816 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4817 +Name = "shallow water" +Flags = {Clip,Unmove} + +TypeID = 4818 +Name = "shallow water" +Flags = {Clip,Unmove} + +TypeID = 4819 +Name = "shallow water" +Flags = {Clip,Unmove} + +TypeID = 4820 +Name = "shallow water" +Flags = {Clip,Unmove} + +TypeID = 4821 +Name = "ploughed soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=180} + +TypeID = 4822 +Name = "ploughed soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=180} + +TypeID = 4823 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 4824 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 4825 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 4826 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 4827 +Name = "whisper moss" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 4828 +Name = "a flask of cough syrup" +Description = "It smells like herbs" +Flags = {Take} +Attributes = {Weight=300} + +TypeID = 4829 +Name = "a witches cap mushroom" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 4830 +Name = "witches mushrooms" +Flags = {Bottom,Chest,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=3919} + +TypeID = 4831 +Name = "an old parchment" +Description = "It is covered with foreign symbols" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 4832 +Name = "a giant ape's hair" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4833 +Name = "a giant footprint" +Flags = {Bottom,Chest,Unmove,Disguise} +Attributes = {DisguiseTarget=2753} + +TypeID = 4834 +Name = "a family brooch" +Description = "The emblem of a dwarven family is engraved on it" +Flags = {Take} +Attributes = {Weight=110} + +TypeID = 4835 +Name = "a snake destroyer" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=6600} + +TypeID = 4836 +Name = "a spectral dress" +Flags = {Take} +Attributes = {Weight=1000,SlotType=BODY} + +TypeID = 4837 +Name = "an icicle" +Description = "It is melting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=1900,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 4838 +Name = "strange powder" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 4839 +Name = "a hydra egg" +Flags = {Take} +Attributes = {Weight=5000} + +TypeID = 4840 +Name = "a spectral stone" +Description = "It is pulsating with spectral energy" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=250,Brightness=2,LightColor=29} + +TypeID = 4841 +Name = "a memory stone" +Flags = {Take} +Attributes = {Weight=250} + +TypeID = 4842 +Name = "a sheet of tracing paper" +Description = "It is blank" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=100} + +TypeID = 4843 +Name = "a sheet of tracing paper" +Description = "It contains some strange symbols of the lizard language" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 4844 +Name = "an elven poetry book" +Description = "It contains a collection of beautiful elven poems" +Flags = {Take} +Attributes = {Weight=1300} + +TypeID = 4845 +Name = "a dwarven pickaxe" +Description = "It is a masterpiece of dwarvish smithery and made of especially hard steel" +Flags = {Take} +Attributes = {Weight=6000} + +TypeID = 4846 +Name = "a wrinkled parchment" +Description = "It is covered with strange numbers" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 4847 +Name = "a funeral urn" +Description = "It contains the ashes of a lizard high priest" +Flags = {Take} +Attributes = {Weight=10000} + +TypeID = 4848 +Name = "a small cask" +Description = "It is filled with the blood of the snake god" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=BLOOD} + +TypeID = 4849 +Name = "wooden trash" +Description = "The blood of the snake god is pouring out" +Flags = {Unpass,Unmove,Unlay,Expire} +Attributes = {ExpireTarget=4848,TotalExpireTime=120} + +TypeID = 4850 +Name = "the statue of the snake god" +Description = "It is emitting an eerie light" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=3,LightColor=102} + +TypeID = 4851 +Name = "a smashed stone head" +Description = "It seems to repair itself rapidly" +Flags = {Bottom,Unpass,Unmove,Unlay,Expire} +Attributes = {ExpireTarget=4850,TotalExpireTime=60} + +TypeID = 4852 +Name = "an ectoplasm container" +Description = "It is empty" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=600} + +TypeID = 4853 +Name = "an ectoplasm container" +Description = "It is filled with ectoplasm" +Flags = {Take} +Attributes = {Weight=600} + +TypeID = 4854 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4855 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4856 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4857 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4858 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4859 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4860 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4861 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4862 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4863 +Name = "a butterfly conservation kit" +Description = "It is empty" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=700} + +TypeID = 4864 +Name = "a butterfly conservation kit" +Description = "It contains a red butterfly" +Flags = {Take} +Attributes = {Weight=800} + +TypeID = 4865 +Name = "a butterfly conservation kit" +Description = "It contains a purple butterfly" +Flags = {Take} +Attributes = {Weight=800} + +TypeID = 4866 +Name = "a butterfly conservation kit" +Description = "It contains a blue butterfly" +Flags = {Take} +Attributes = {Weight=800} + +TypeID = 4867 +Name = "a botanist's container" +Description = "It is empty" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=1800} + +TypeID = 4868 +Name = "a botanist's container" +Description = "It holds a sample of the jungle bells plant" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4869 +Name = "a botanist's container" +Description = "It holds a sample of the giant jungle rose" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4870 +Name = "a botanist's container" +Description = "It holds a sample of the witches cauldron plant" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4871 +Name = "an explorer brooch" +Description = "It is the official badge of the explorer society" +Flags = {Take} +Attributes = {Weight=90} + +TypeID = 4872 +Name = "an ice pick" +Description = "It might come in handy in cold regions" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=7000} + +TypeID = 4873 +Name = "a hydra's nest" +Flags = {Bottom,Chest,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=5676} + +TypeID = 4874 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4875 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4876 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4877 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4878 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4879 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4880 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4881 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 4882 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4883 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4884 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 4885 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4886 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4887 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4888 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4889 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4890 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4891 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4892 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4893 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4894 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4895 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4896 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4897 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4898 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4899 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4900 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4901 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4902 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4903 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4904 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4905 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4906 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4907 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4908 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4909 +Name = "a ship rail" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4910 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4911 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 4912 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4913 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4914 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 4915 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4916 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4917 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4918 +Name = "a ship rail" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4919 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4920 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4921 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4922 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4923 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4924 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4925 +Name = "a ship railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4926 +Name = "a ship railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4927 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4928 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4929 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4930 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4931 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4932 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4933 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4934 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4935 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4936 +Name = "a ship railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4937 +Name = "a ship railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4938 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4939 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4940 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4941 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4942 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4943 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4944 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4945 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4946 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4947 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4948 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4949 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4950 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4951 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4952 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4953 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4954 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4955 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4956 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4957 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4958 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4959 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4960 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4961 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4962 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4963 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4964 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4965 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4966 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4967 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4968 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4969 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4970 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4971 +Name = "a ventilation grille" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 4972 +Name = "a bollard" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4973 +Name = "an anchor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4974 +Name = "a figurehead" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4975 +Name = "a figurehead" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4976 +Name = "an anchor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4977 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4978 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4979 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4980 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4981 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4982 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4983 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4984 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4985 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4986 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4987 +Name = "a cleat" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4988 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4989 +Name = "a white flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4990 +Name = "a pirate flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4991 +Name = "a dead butterfly" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20,ExpireTarget=4378,TotalExpireTime=1200} + +TypeID = 4992 +Name = "a dead butterfly" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20,ExpireTarget=4378,TotalExpireTime=1200} + +TypeID = 4993 +Name = "a dead butterfly" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20,ExpireTarget=4378,TotalExpireTime=1200} + +TypeID = 4994 +Name = "some sharp icicles" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4995 +Name = "a canopic jar" +Description = "You feel an eerie presence" +Flags = {Unpass,Unmove,Unlay,Destroy} +Attributes = {DestroyTarget=4996} + +TypeID = 4996 +Name = "the remains of a canopic jar" +Flags = {Unpass,Unmove,Unlay,Expire} +Attributes = {ExpireTarget=4995,TotalExpireTime=300} + +TypeID = 4997 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4998 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4999 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5000 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 5001 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5002 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5003 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 5004 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 5005 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5006 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5007 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5008 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5009 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5010 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5011 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 5012 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 5013 +Name = "a dead butterfly" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20,ExpireTarget=4378,TotalExpireTime=1200} + +TypeID = 5014 +Name = "a mandrake" +Flags = {Take} +Attributes = {Weight=180} + +TypeID = 5015 +Name = "a skull" +Flags = {Unmove} + +TypeID = 5016 +Name = "a skull" +Flags = {Unmove} + +TypeID = 5017 +Name = "some skulls" +Flags = {Unmove} + +TypeID = 5018 +Name = "some skulls" +Flags = {Unmove} + +TypeID = 5019 +Name = "a burning skull" +Flags = {Unmove,Hang} +Attributes = {Brightness=3,LightColor=199} + +TypeID = 5020 +Name = "a burning skull" +Flags = {Unmove,Hang} +Attributes = {Brightness=3,LightColor=199} + +TypeID = 5021 +Name = "an orichalcum pearl" +Flags = {Cumulative,Take} +Attributes = {Weight=30} + +TypeID = 5022 +Name = "a magic forcefield" +Description = "You can see the other side through it" +Flags = {Bottom,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 5023 +Name = "a magic forcefield" +Description = "You can see the other side through it" +Flags = {Bottom,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 5024 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=215} + +TypeID = 5025 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=208} + +TypeID = 5026 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=201} + +TypeID = 5027 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=194} + +TypeID = 5028 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=187} + +TypeID = 5029 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=180} + +TypeID = 5030 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=214} + +TypeID = 5031 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=207} + +TypeID = 5032 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=200} + +TypeID = 5033 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=193} + +TypeID = 5034 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=186} + +TypeID = 5035 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=213} + +TypeID = 5036 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=206} + +TypeID = 5037 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=199} + +TypeID = 5038 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=192} + +TypeID = 5039 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=212} + +TypeID = 5040 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=205} + +TypeID = 5041 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=198} + +TypeID = 5042 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=211} + +TypeID = 5043 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=204} + +TypeID = 5044 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=210} + +TypeID = 5045 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5046 +Name = "a monkey statue" +Description = "The words 'See no evil' are engraved on it" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3142} + +TypeID = 5047 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5048 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5049 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5050 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5051 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5052 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5053 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5054 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5055 +Name = "a monkey statue" +Description = "The words 'Hear no evil' are engraved on it" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3142} + +TypeID = 5056 +Name = "a monkey statue" +Description = "The words 'Speak no evil' are engraved on it" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3142} + +TypeID = 5057 +Name = "a snake head" +Description = "It is emitting poisonous clouds" +Flags = {Unmove} + +TypeID = 5058 +Name = "a small snake head" +Flags = {Unmove} + +TypeID = 5059 +Name = "a small snake head" +Description = "It is emitting poisonous clouds" +Flags = {Unmove} + +TypeID = 5060 +Name = "a small snake head" +Flags = {Unmove} + +TypeID = 5061 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5062 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5063 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5064 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5065 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5066 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5067 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5068 +Name = "electric sparks" +Flags = {Unmove} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 5069 +Name = "electric sparks" +Flags = {Unmove} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 5070 +Name = "electric sparks" +Flags = {Unmove} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 5071 +Name = "electric iron bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5072 +Name = "electric iron bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5073 +Name = "electric iron bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5074 +Name = "a lava fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 5075 +Name = "a lava fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 5076 +Name = "a stony pond" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5077 +Name = "a stony pond" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5078 +Name = "a stony pond" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5079 +Name = "a stony pond" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5080 +Name = "a panda teddy" +Flags = {UseEvent,Take} +Attributes = {Weight=600} + +TypeID = 5081 +Name = "a ladder" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=160} + +TypeID = 5082 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5083 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5084 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5085 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5086 +Name = "a furniture package" +Description = "It contains a construction kit for a monkey statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 5087 +Name = "a furniture package" +Description = "It contains a construction kit for a monkey statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 5088 +Name = "a furniture package" +Description = "It contains a construction kit for a monkey statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 5089 +Name = "a butterfly conservation kit" +Description = "It contains a rare yellow butterfly" +Flags = {Take} +Attributes = {Weight=800} + +TypeID = 5090 +Name = "a treasure map" +Flags = {Text,Take} +Attributes = {Weight=830} + +TypeID = 5091 +Name = "a banana tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5092 +Name = "a banana tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5093 +Name = "a banana tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5094 +Name = "a banana tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5095 +Name = "a banana tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5096 +Name = "a mango" +Flags = {Cumulative,Take} +Attributes = {Nutrition=4,Weight=180} + +TypeID = 5097 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5098 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5099 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5100 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5101 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5102 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5103 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 5104 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5105 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5106 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5107 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5108 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5109 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5110 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5111 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5112 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 5113 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5114 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5115 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5116 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5117 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5118 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5119 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5120 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5121 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 5122 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5123 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5124 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5125 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5126 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5127 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5128 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5129 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5130 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 5131 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5132 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5133 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5134 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5135 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5136 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5137 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5138 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5139 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5140 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5141 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5142 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5143 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5144 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5145 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5146 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5147 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5148 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5149 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5150 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5151 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5152 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5153 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5154 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5155 +Name = "a mango tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5156 +Name = "a mango tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5157 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5158 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5159 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5160 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5161 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5162 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5163 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5164 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5165 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5166 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5167 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5168 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5169 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5170 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5171 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5172 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5173 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5174 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5175 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5176 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5177 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5178 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5179 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5180 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5181 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5182 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5183 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5184 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5185 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5186 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5187 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5188 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5189 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5190 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5191 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5192 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5193 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5194 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5195 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5196 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5197 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5198 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5199 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5200 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5201 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5202 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5203 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5204 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5205 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5206 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5207 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5208 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5209 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5210 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5211 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5212 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5213 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5214 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5215 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5216 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5217 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5218 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5219 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5220 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5221 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5222 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5223 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5224 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5225 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5226 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5227 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5228 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5230 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5231 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5232 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5233 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5234 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5235 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5236 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5237 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5238 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5239 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5240 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5241 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5242 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5243 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5244 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5245 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5246 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5247 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5248 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5249 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5250 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5251 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5252 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5253 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5254 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5255 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5256 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5257 +Name = "stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 5258 +Name = "stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 5259 +Name = "stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 5260 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5261 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5262 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5263 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5264 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5265 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5266 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5267 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5268 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5269 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5270 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5271 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5272 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5273 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5274 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5275 +Name = "a wooden window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5276 +Name = "a wooden window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5277 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5278 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5279 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5280 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5281 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5282 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5283 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5284 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5285 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5286 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5287 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5288 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5289 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5290 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5291 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5292 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 5293 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5294 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 5295 +Name = "a wooden plank" +Flags = {Unmove} + +TypeID = 5296 +Name = "a wooden planks" +Flags = {Unmove} + +TypeID = 5297 +Name = "a wooden plank" +Flags = {Unmove} + +TypeID = 5298 +Name = "a wooden planks" +Flags = {Unmove} + +TypeID = 5299 +Name = "a wooden plank" +Flags = {Unmove} + +TypeID = 5300 +Name = "a wooden plank" +Flags = {Unmove} + +TypeID = 5301 +Name = "a wooden planks" +Flags = {Unmove} + +TypeID = 5302 +Name = "a white stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5303 +Name = "a white stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5304 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5305 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5306 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5307 +Name = "a white stone pillar" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5308 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5309 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5310 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5311 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5312 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5313 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5314 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5315 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5316 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5317 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5318 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5319 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5320 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5321 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5322 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5323 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5324 +Name = "a wooden column" +Flags = {Top,Unmove,Unlay} + +TypeID = 5325 +Name = "a wooden column" +Flags = {Top,Unmove,Unlay} + +TypeID = 5326 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5327 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5328 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5329 +Name = "a wooden column" +Flags = {Top,Unmove,Unlay} + +TypeID = 5330 +Name = "a wooden column" +Flags = {Top,Unmove,Unlay} + +TypeID = 5331 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5332 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5333 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5334 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5335 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5336 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5337 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5338 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5339 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5340 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5341 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5342 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5343 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5344 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5345 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5346 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5347 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5348 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5349 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5350 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5351 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5352 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5353 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5354 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5355 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5356 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5357 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5358 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5359 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5360 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5361 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5362 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5363 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5364 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5365 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5366 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5367 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5368 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5369 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5370 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5371 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5372 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5373 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5374 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5375 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5376 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5377 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5378 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5379 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5380 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5381 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5382 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5383 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5384 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5385 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5386 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5387 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5388 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5389 +Name = "a pawpaw tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5390 +Name = "a pawpaw tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5391 +Name = "a mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5392 +Name = "a mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5393 +Name = "a dry mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5394 +Name = "a mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5395 +Name = "a mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5396 +Name = "a mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5397 +Name = "a dry mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5398 +Name = "a mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5399 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5400 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5401 +Name = "a bamboo lamp" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=7,LightColor=207} + +TypeID = 5402 +Name = "a bamboo lamp" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=7,LightColor=207} + +TypeID = 5403 +Name = "a bamboo lamp" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=7,LightColor=207} + +TypeID = 5404 +Name = "ocean floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5405 +Name = "ocean floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5406 +Name = "ocean floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5407 +Name = "ocean floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5408 +Name = "ocean floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5409 +Name = "ocean floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5410 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5411 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5412 +Name = "a coral" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5413 +Name = "a coral" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5414 +Name = "a coral" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5415 +Name = "a coral" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5416 +Name = "a starfish" +Flags = {Unmove} + +TypeID = 5417 +Name = "a sea anemone" +Flags = {Unmove} + +TypeID = 5418 +Name = "bubbles" +Flags = {Top,Unmove} + +TypeID = 5419 +Name = "bubbles" +Flags = {Top,Unmove} + +TypeID = 5420 +Name = "kelp" +Flags = {Unpass,Unmove} + +TypeID = 5421 +Name = "a coral" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5422 +Name = "a coral" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5423 +Name = "a water wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5424 +Name = "a water wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5425 +Name = "an old steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5426 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5427 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5428 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5429 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5430 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5431 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5432 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5433 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5434 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5435 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5436 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5437 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5438 +Name = "an old steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5439 +Name = "an old steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5440 +Name = "an old steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5441 +Name = "the remains of a mast" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5442 +Name = "the remains of a mast" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5443 +Name = "a rusty anchor" +Flags = {Unpass,Unmove} + +TypeID = 5444 +Name = "the remains of a mast" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 5445 +Name = "the remains of a mast" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 5446 +Name = "the remains of a mast" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 5447 +Name = "the remains of a mast" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 5448 +Name = "the remains of a mast" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 5449 +Name = "a wrecked ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5450 +Name = "a wrecked figurehead" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5451 +Name = "a wrecked ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5452 +Name = "a wrecked ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5453 +Name = "a wrecked ship cabin railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5454 +Name = "a wrecked ship cabin railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5455 +Name = "a wrecked ship hull" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5456 +Name = "a wrecked ship hull" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5457 +Name = "a wrecked ship hull" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5458 +Name = "a wrecked ship hull" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5459 +Name = "a wrecked ventilation grill" +Flags = {Unmove} + +TypeID = 5460 +Name = "a helmet of the deep" +Description = "Enables underwater exploration" +Flags = {Take,Armor} +Attributes = {Weight=21000,SlotType=HEAD,ArmorValue=2,AbsorbDrown=100} + +TypeID = 5461 +Name = "pirate boots" +Flags = {Take,Armor} +Attributes = {Weight=800,SlotType=FEET,ArmorValue=2} + +TypeID = 5462 +Name = "a sugar cane" +Description = "It has just been harvested" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=5470,TotalExpireTime=240} + +TypeID = 5463 +Name = "a sugar cane" +Description = "It can be harvested" +Flags = {Unmove,Avoid} + +TypeID = 5464 +Name = "a burning sugar cane" +Flags = {Unpass,Unmove,Expire} +Attributes = {ExpireTarget=5463,TotalExpireTime=10} + +TypeID = 5465 +Name = "a sugar cane" +Flags = {Unpass,Unmove} + +TypeID = 5466 +Name = "a bunch of sugar cane" +Flags = {UseEvent,Cumulative,MultiUse,Take} +Attributes = {Weight=2250} + +TypeID = 5467 +Name = "a fire bug" +Description = "This strange creature has the tendency to set certain things on fire" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=3050,Brightness=3,LightColor=206} + +TypeID = 5468 +Name = "a distilling machine" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5469 +Name = "a distilling machine" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5470 +Name = "a sugar cane" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=5465,TotalExpireTime=240} + +TypeID = 5471 +Name = "a ball on chains" + +TypeID = 5472 +Name = "a ball on chains" + +TypeID = 5473 +Name = "an iron maiden" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5474 +Name = "an iron maiden" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5475 +Name = "a pillory" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5476 +Name = "a pillory" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5477 +Name = "a barred window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5478 +Name = "a barred window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5479 +Name = "a cat's paw" +Flags = {Take} +Attributes = {Weight=1000} + +TypeID = 5480 +Name = "a shackles" +Flags = {Unpass,Unmove} + +TypeID = 5481 +Name = "a shackles" +Flags = {Unpass,Unmove} + +TypeID = 5482 +Name = "wall chains" +Flags = {Unmove} + +TypeID = 5483 +Name = "wall chains" +Flags = {Unmove} + +TypeID = 5484 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5485 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5486 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5487 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5488 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5489 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5490 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5491 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5492 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5493 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5494 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5495 +Name = "a straw mat" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedFree=5501} + +TypeID = 5496 +Name = "a straw mat" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedFree=5502} + +TypeID = 5497 +Name = "a straw mat" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedFree=5499} + +TypeID = 5498 +Name = "a straw mat" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedFree=5500} + +TypeID = 5499 +Name = "a straw mat" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} # TODO: Every straw mat should be a bed to lay in house +Attributes = {BedDirection=EAST,BedTarget=5497} + +TypeID = 5500 +Name = "a straw mat" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedTarget=5498} + +TypeID = 5501 +Name = "a straw mat" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedTarget=5495} + +TypeID = 5502 +Name = "a straw mat" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedTarget=5496} + +TypeID = 5503 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5504 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5505 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5506 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5507 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5508 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5509 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5510 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5511 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5512 +Name = "a distilling machine" +Flags = {Bottom,Unpass,Unmove,Unlay,Expire} +Attributes = {FluidSource=RUM,ExpireTarget=5468,TotalExpireTime=10} + +TypeID = 5513 +Name = "a distilling machine" +Flags = {Bottom,Unpass,Unmove,Unlay,Expire} +Attributes = {FluidSource=RUM,ExpireTarget=5469,TotalExpireTime=10} + +TypeID = 5514 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5515 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5516 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5517 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5518 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5519 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5520 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5521 +Name = "a dead quara pincher" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5536,TotalExpireTime=900} + +TypeID = 5522 +Name = "a dead quara mantassin" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5532,TotalExpireTime=900} + +TypeID = 5523 +Name = "a dead quara constrictor" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5530,TotalExpireTime=900} + +TypeID = 5524 +Name = "a dead quara hydromancer" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5528,TotalExpireTime=900} + +TypeID = 5525 +Name = "a dead quara predator" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5534,TotalExpireTime=900} + +TypeID = 5526 +Name = "a demon dust" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=24,FluidSource=BLOOD,ExpireTarget=5527,TotalExpireTime=900} + +TypeID = 5527 +Name = "a demon dust" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5528 +Name = "a dead quara hydromancer" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5529,TotalExpireTime=900} + +TypeID = 5529 +Name = "a dead quara hydromancer" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5530 +Name = "a dead quara constrictor" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5531,TotalExpireTime=900} + +TypeID = 5531 +Name = "a dead quara constrictor" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5532 +Name = "a dead quara mantassin" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5533,TotalExpireTime=900} + +TypeID = 5533 +Name = "a dead quara mantassin" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5534 +Name = "a dead quara predator" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5535,TotalExpireTime=900} + +TypeID = 5535 +Name = "a dead quara predator" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5536 +Name = "a dead quara pincher" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5537,TotalExpireTime=900} + +TypeID = 5537 +Name = "a dead quara pincher" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5538 +Name = "a rum cask" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=RUM} + +TypeID = 5539 +Name = "a dead carrion worm" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5540,TotalExpireTime=900} + +TypeID = 5540 +Name = "a dead carrion worm" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5541,TotalExpireTime=600} + +TypeID = 5541 +Name = "a dead carrion worm" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4000,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5542 +Name = "a rope-ladder" +Flags = {Bottom,UseEvent,ForceUse,Unmove} + +TypeID = 5543 +Name = "a cart" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5544 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 5545 +Name = "a cart" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5546 +Name = "a cart" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5547 +Name = "a pile of sugar cane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5548 +Name = "a pile of sugar cane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5549 +Name = "a pile of sugar cane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5550 +Name = "a pile of sugar cane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5551 +Name = "a pile of sugar cane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5552 +Name = "a rum flask" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=280} + +TypeID = 5553 +Name = "a small fish" +Flags = {Unmove} + +TypeID = 5554 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5555 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5556 +Name = "a mossy wall" +Flags = {Unmove} + +TypeID = 5557 +Name = "a mossy wall" +Flags = {Unmove} + +TypeID = 5558 +Name = "a mossy wall" +Flags = {Unmove} + +TypeID = 5559 +Name = "a mossy wall" +Flags = {Unmove} + +TypeID = 5560 +Name = "a small branches" +Flags = {Unmove} + +TypeID = 5561 +Name = "a small branches" +Flags = {Unmove} + +TypeID = 5562 +Name = "a slimy wall" +Flags = {Unmove} + +TypeID = 5563 +Name = "a slimy wall" +Flags = {Unmove} + +TypeID = 5564 +Name = "a slain pirate skeleton" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=40000,ExpireTarget=0,TotalExpireTime=900} + +TypeID = 5565 +Name = "a slain pirate ghost" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=20000,ExpireTarget=5566,TotalExpireTime=900} + +TypeID = 5566 +Name = "a slain pirate ghost" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=5567,TotalExpireTime=600} + +TypeID = 5567 +Name = "a slain pirate ghost" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5568 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5569 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5570 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5571 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5572 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5573 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5574 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5575 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5576 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5577 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5578 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5579 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5580 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5581 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5582 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5583 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5584 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5585 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5586 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5587 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5588 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5589 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5590 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5591 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5592 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5593 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5594 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5595 +Name = "a crossed weapons" +Flags = {Unmove,Hang} + +TypeID = 5596 +Name = "a crossed weapons" +Flags = {Unmove,Hang} + +TypeID = 5597 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5598 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5599 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5600 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5601 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5602 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5603 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5604 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5605 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5606 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5607 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5608 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5609 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5610 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5611 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5612 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5613 +Name = "a pirate flag" +Flags = {Unmove} + +TypeID = 5614 +Name = "a pirate flag" +Flags = {Unmove} + +TypeID = 5615 +Name = "a pirate tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 5616 +Name = "a pirate flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5617 +Name = "a pirate flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5618 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5619 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5620 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5621 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5622 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5623 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5624 +Name = "a dead tortoise" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=6,Weight=1000,FluidSource=BLOOD,ExpireTarget=5625,TotalExpireTime=900} + +TypeID = 5625 +Name = "a dead tortoise" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1000,ExpireTarget=5626,TotalExpireTime=600} + +TypeID = 5626 +Name = "a dead tortoise" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1000,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5627 +Name = "a dead thornback tortoise" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=6,FluidSource=BLOOD,ExpireTarget=5628,TotalExpireTime=900} + +TypeID = 5628 +Name = "a dead thornback tortoise" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=5629,TotalExpireTime=600} + +TypeID = 5629 +Name = "a dead thornback tortoise" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=6000,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5630 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5631 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5632 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5633 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5634 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5635 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5636 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5637 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5638 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5639 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5640 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5641 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5642 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5643 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5644 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5645 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5646 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5647 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5648 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5649 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5650 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5651 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5652 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5653 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5654 +Name = "a map" +Flags = {Unmove,Unlay} + +TypeID = 5655 +Name = "a map" +Flags = {Unmove,Unlay} + +TypeID = 5656 +Name = "a map" +Flags = {Unmove,Unlay} + +TypeID = 5657 +Name = "a map" +Flags = {Unmove,Unlay} + +TypeID = 5658 +Name = "a blooming griffinclaw" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5659 +Name = "a water trickle" +Flags = {Unmove} + +TypeID = 5660 +Name = "a water trickle" +Flags = {Unmove} + +TypeID = 5661 +Name = "a water trickle" +Flags = {Unmove} + +TypeID = 5662 +Name = "a water trickle" +Flags = {Unmove} + +TypeID = 5663 +Name = "a water trickle" +Flags = {Unmove} + +TypeID = 5664 +Name = "a water trickle" +Flags = {Unmove} + +TypeID = 5665 +Name = "a dead mammoth" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=5666,TotalExpireTime=900} + +TypeID = 5666 +Name = "a dead mammoth" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=5667,TotalExpireTime=600} + +TypeID = 5667 +Name = "a dead mammoth" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5668 +Name = "a mysterious voodoo skull" +Flags = {UseEvent,Take} +Attributes = {Weight=1400} + +TypeID = 5669 +Name = "a enigmatic voodoo skull" +Description = "It is not time yet" +Flags = {Take,Expire} +Attributes = {Weight=1400,ExpireTarget=5668,TotalExpireTime=72000} + +TypeID = 5670 +Name = "a pirate statue" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5671 +Name = "a pirate statue" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5672 +Name = "a pirate statue" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5673 +Name = "a pirate statue" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5674 +Name = "a treasure chest" +Flags = {Unmove,Avoid} + +TypeID = 5675 +Name = "a treasure chest" +Flags = {Unmove,Avoid} + +TypeID = 5676 +Name = "a hydra's nest" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5677 +Name = "a tortoise's nest" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5678 +Name = "a tortoise egg" +Flags = {Cumulative,Take} +Attributes = {Nutrition=8,Weight=30} + +TypeID = 5679 +Name = "a shell" +Flags = {Unmove} + +TypeID = 5680 +Name = "a spiral shell" +Flags = {Unmove} + +TypeID = 5681 +Name = "some shells" +Flags = {Unmove} + +TypeID = 5682 +Name = "a piece of a shell" +Flags = {Unmove} + +TypeID = 5683 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5684 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5685 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5686 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5687 +Name = "a dry griffinclaw" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5688 +Name = "a dead blood crab" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=5689,TotalExpireTime=900} + +TypeID = 5689 +Name = "a dead blood crab" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=5690,TotalExpireTime=600} + +TypeID = 5690 +Name = "a dead blood crab" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5691 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=110} + +TypeID = 5692 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5693 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5694 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5695 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5696 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5697 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5698 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5699 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5700 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5701 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5702 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5703 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5704 +Name = "a shark fin" +Flags = {Unmove,Unlay} + +TypeID = 5705 +Name = "a shark fin" +Flags = {Unmove,Unlay} + +TypeID = 5706 +Name = "a treasure map" +Description = "It obviously shows Treasure Island including a big, red cross" +Flags = {Take} +Attributes = {Weight=830} + +TypeID = 5707 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5708 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5709 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5710 +Name = "a light shovel" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=1500} + +TypeID = 5711 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5712 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5713 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5714 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5715 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5716 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5717 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5718 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5719 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5720 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5721 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5722 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5723 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5724 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5725 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5726 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5727 +Name = "a dead seagull" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=1000,FluidSource=BLOOD,ExpireTarget=5728,TotalExpireTime=600} + +TypeID = 5728 +Name = "a dead seagull" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=800,ExpireTarget=5729,TotalExpireTime=600} + +TypeID = 5729 +Name = "a dead seagull" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=500,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5730 +Name = "a chest" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2480} + +TypeID = 5731 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=130} + +TypeID = 5732 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5733 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5734 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5735 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5736 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5737 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5738 +Name = "a shark fin" +Flags = {Unmove} + +TypeID = 5739 +Name = "an antic well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5740 +Name = "an antic well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5741 +Name = "a skull helmet" +Flags = {Take,Armor} +Attributes = {Weight=4200,SlotType=HEAD,ArmorValue=9} + +TypeID = 5742 +Name = "a chest" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2482} + +TypeID = 5743 +Name = "wooden floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5744 +Name = "wooden floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5745 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5746 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5747 +Name = "an earth pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5748 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5749 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5750 +Name = "an earth pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5751 +Name = "an earth pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5752 +Name = "an earth pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5753 +Name = "an earth pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5754 +Name = "an earth pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5755 +Name = "a turtle" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 5756 +Name = "a turtle" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 5757 +Name = "a turtle" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5758 +Name = "a turtle" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5759 +Name = "a turtle" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5760 +Name = "a turtle" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5761 +Name = "a turtle" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5762 +Name = "a dead quara constrictor" +Flags = {Container,Corpse} +Attributes = {Capacity=10,FluidSource=BLOOD} + +TypeID = 5763 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=110} + +TypeID = 5764 +Name = "a ventilation grille" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5765 +Name = "a dead toad" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5766,TotalExpireTime=600} + +TypeID = 5766 +Name = "a dead toad" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=5767,TotalExpireTime=600} + +TypeID = 5767 +Name = "a dead toad" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5768 +Name = "a drawbridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=90} + +TypeID = 5769 +Name = "a drawbridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=90} + +TypeID = 5770 +Name = "a drawbridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=90} + +TypeID = 5771 +Name = "metal fittings" +Flags = {Clip,Unmove} + +TypeID = 5772 +Name = "metal fittings" +Flags = {Clip,Unmove} + +TypeID = 5773 +Name = "metal fittings" +Flags = {Clip,Unmove} + +TypeID = 5774 +Name = "metal fittings" +Flags = {Clip,Unmove} + +TypeID = 5775 +Name = "a chest" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2481} + +TypeID = 5776 +Name = "a Sabrehaven talon" +Description = "Rumours say that the Gods enchanted these talons for the greatest good, or the greatest evil achievements" +Flags = {Cumulative,Take,Disguise} +Attributes = {DisguiseTarget=3034,Weight=20} + +TypeID = 5777 +Name = "a target board" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5778 +Name = "a target board" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5779 +Name = "a stuck arrow" +Flags = {Unmove} + +TypeID = 5780 +Name = "a stuck arrow" +Flags = {Unmove} + +TypeID = 5781 +Name = "a stuck arrow" +Flags = {Unmove} + +TypeID = 5782 +Name = "a stuck arrow" +Flags = {Unmove} + +TypeID = 5783 +Name = "a stuck arrow" +Flags = {Unmove} + +TypeID = 5784 +Name = "a stuck arrow" +Flags = {Unmove} + +TypeID = 5785 +Name = "a medal of honour" +Flags = {Text,WriteOnce,Take} +Attributes = {MaxLength=99,Weight=1000} + +TypeID = 5786 +Name = "a wooden whistle" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 5787 +Name = "a training dummy" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5788 +Name = "a training dummy" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5789 +Name = "a stuck axe" +Flags = {Unmove} + +TypeID = 5790 +Name = "a stuck axe" +Flags = {Unmove} + +TypeID = 5791 +Name = "a stuffed dragon" +Flags = {UseEvent,Take} +Attributes = {Weight=850} + +TypeID = 5792 +Name = "a dice" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 5793 +Name = "a dice" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 5794 +Name = "a dice" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 5795 +Name = "a dice" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 5796 +Name = "a dice" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 5797 +Name = "a dice" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 5798 +Name = "an abacus" +Description = "If you use it wisely, you may write yourself into the history books" +Flags = {Take} +Attributes = {Weight=1200} + +TypeID = 5799 +Name = "a golden figurine" +Flags = {Text,WriteOnce,Take} +Attributes = {MaxLength=99,Weight=1500} + +TypeID = 5800 +Name = "a grappling hook" +Flags = {Unmove} + +TypeID = 5801 +Name = "a key ring" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=50} + +TypeID = 5802 +Name = "a message in a bottle" +Flags = {Take} +Attributes = {Weight=1200} + +TypeID = 5803 +Name = "an arbalest" +Description = "It is a bit heavy due to the iron mounting, but very precise" +Flags = {Take,Distance} +Attributes = {Weight=9500,SlotType=TWOHANDED,Range=7,AmmoType=BOLT} + +TypeID = 5804 +Name = "a nose ring" +Description = "It was the favourite trinket of the famous Horned Fox" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 5805 +Name = "a golden goblet" +Flags = {Text,WriteOnce,Take} +Attributes = {MaxLength=99,Weight=1500} + +TypeID = 5806 +Name = "a silver goblet" +Flags = {Text,WriteOnce,Take} +Attributes = {MaxLength=99,Weight=1500} + +TypeID = 5807 +Name = "a bronze goblet" +Flags = {Text,WriteOnce,Take} +Attributes = {MaxLength=99,Weight=1500} + +TypeID = 5808 +Name = "Orshabaal's brain" +Flags = {Take} +Attributes = {Weight=3500} + +TypeID = 5809 +Name = "a soul stone" +Description = "It contains the essence of countless tormented souls" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 5810 +Name = "a voodoo doll" +Description = "It looks like a small pirate" +Flags = {Take} +Attributes = {Weight=650} + +TypeID = 5811 +Name = "a mermaid" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5812 +Name = "a skull candle" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=5813,Weight=2200,Brightness=3,LightColor=206,ExpireTarget=3114,TotalExpireTime=3000} + +TypeID = 5813 +Name = "a skull candle" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=5812,Weight=2200,Brightness=0,LightColor=215} + +TypeID = 5814 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unpass,Unmove} + +TypeID = 5815 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 5816 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5817 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5818 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5819 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5820 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5821 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5822 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5823 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5824 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5825 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5826 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5827 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5828 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5829 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5830 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5831 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5832 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5833 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5834 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5835 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5836 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5837 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5838 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5839 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5840 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5841 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5842 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5843 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5844 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5845 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5846 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5847 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5848 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5849 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5850 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5851 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5852 +Name = "a weapon rack" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5853 +Name = "a weapon rack" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5854 +Name = "a lance" +Flags = {Unmove} + +TypeID = 5855 +Name = "a lance" +Flags = {Unmove} + +TypeID = 5856 +Name = "a sword" +Flags = {Unmove} + +TypeID = 5857 +Name = "a sword" +Flags = {Unmove} + +TypeID = 5858 +Name = "a scimitar" +Flags = {Unmove} + +TypeID = 5859 +Name = "a scimitar" +Flags = {Unmove} + +TypeID = 5860 +Name = "an armour rack" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5861 +Name = "an armour rack" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5862 +Name = "an armour rack" +Flags = {Unmove} + +TypeID = 5863 +Name = "an armour rack" +Flags = {Unmove} + +TypeID = 5864 +Name = "" # this is nothing in client + +TypeID = 5865 +Name = "a juice squeezer" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 5866 +Name = "rubble" +Flags = {Top,Unpass,Unmove,Unlay} + +TypeID = 5867 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5868 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5869 +Name = "a poison trickle" +Flags = {Unmove} + +TypeID = 5870 +Name = "a poison trickle" +Flags = {Unmove} + +TypeID = 5871 +Name = "a poison trickle" +Flags = {Unmove} + +TypeID = 5872 +Name = "a poison trickle" +Flags = {Unmove} + +TypeID = 5873 +Name = "a poison trickle" +Flags = {Unmove} + +TypeID = 5874 +Name = "a poison trickle" +Flags = {Unmove} + +TypeID = 5875 +Name = "sniper gloves" +Description = "They are the pride of the paladin guild" +Flags = {Take} +Attributes = {Weight=400} + +TypeID = 5876 +Name = "a lizard leather" +Flags = {Cumulative,Take} +Attributes = {Weight=50} + +TypeID = 5877 +Name = "a green dragon leather" +Flags = {Cumulative,Take} +Attributes = {Weight=50} + +TypeID = 5878 +Name = "a minotaur leather" +Flags = {Cumulative,Take} +Attributes = {Weight=40} + +TypeID = 5879 +Name = "a spider silk" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 5880 +Name = "an iron ore" +Flags = {Cumulative,Take} +Attributes = {Weight=200} + +TypeID = 5881 +Name = "a lizard scale" +Flags = {Cumulative,Take} +Attributes = {Weight=80} + +TypeID = 5882 +Name = "a red dragon scale" +Flags = {Cumulative,Take} +Attributes = {Weight=90} + +TypeID = 5883 +Name = "an ape fur" +Flags = {Cumulative,Take} +Attributes = {Weight=120} + +TypeID = 5884 +Name = "a spirit container" +Description = "It contains pure fighting spirit" +Flags = {Take} +Attributes = {Weight=600} + +TypeID = 5885 +Name = "a flask of warrior's sweat" +Description = "It contains the sweat spilled in many battles and is said to be used for certain perfumes too" +Flags = {Take} +Attributes = {Weight=300} + +TypeID = 5886 +Name = "a spool of yarn" +Description = "It is made from fine spider silk" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 5887 +Name = "a piece of royal steel" +Description = "Even the king would be proud to wear an armour made of this refined steel" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5888 +Name = "a piece of hell steel" +Description = "This rare metal must have been refined in the depths" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5889 +Name = "a piece of draconian steel" +Description = "An armour made of this steel is said to protect against fiery dragon breath" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5890 +Name = "a chicken feather" +Description = "Some thousands of these would probably make an extremely comfortable pillow" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 5891 +Name = "an enchanted chicken wing" +Description = "It is said to make your feet fly" +Flags = {Cumulative,Take} +Attributes = {Weight=1000} + +TypeID = 5892 +Name = "a huge chunk of crude iron" +Flags = {Take} +Attributes = {Weight=5000} + +TypeID = 5893 +Name = "a perfect behemoth fang" +Description = "Collectors all around the world crave for this item" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5894 +Name = "a bat wing" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 5895 +Name = "a fish fin" +Description = "It once belonged to a mighty creature of the deep" +Flags = {Cumulative,Take} +Attributes = {Weight=120} + +TypeID = 5896 +Name = "a bear paw" +Flags = {Cumulative,Take} +Attributes = {Weight=120} + +TypeID = 5897 +Name = "a wolf paw" +Flags = {Cumulative,Take} +Attributes = {Weight=80} + +TypeID = 5898 +Name = "a beholder's eye" +Description = "You could swear it just winked at you" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5899 +Name = "a turtle shell" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5900 +Name = "a dwarven beard" +Description = "It was once worn proudly by a dwarfish warrior - or maiden" +Flags = {Cumulative,Take} +Attributes = {Weight=50} + +TypeID = 5901 +Name = "wood" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5902 +Name = "a honeycomb" +Description = "Some people swear it makes an excellent glue" +Flags = {Cumulative,Take} +Attributes = {Weight=80} + +TypeID = 5903 +Name = "Ferumbras' hat" +Description = "It is the proof that Ferumbras has fallen. For now. The Edron Academy should be interested in this" +Flags = {Take,Armor} +Attributes = {Weight=850,SlotType=HEAD,ArmorValue=1} + +TypeID = 5904 +Name = "a magic sulphur" +Description = "It smells rather badly but is said to be an important catalyst for magical rituals" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 5905 +Name = "a vampire dust" +Description = "Sun can be a merciless killer, but so can you" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 5906 +Name = "a demon dust" +Description = "It reeks of hatred and malice" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 5907 +Name = "a slingshot" +Description = "A hermit near Carlin might be able to tell you more about it" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 5908 +Name = "an obsidian knife" +Description = "Sharp and light, this is a useful tool for tanners, doctors and assassins" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 5909 +Name = "a white piece of cloth" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5910 +Name = "a green piece of cloth" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5911 +Name = "a red piece of cloth" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5912 +Name = "a blue piece of cloth" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5913 +Name = "a brown piece of cloth" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5914 +Name = "a yellow piece of cloth" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5915 +Name = "a large throne" +Flags = {Unmove,Avoid} + +TypeID = 5916 +Name = "a large throne" +Flags = {Unmove,Avoid} + +TypeID = 5917 +Name = "a bandana" +Description = "It is quite fashionable as well as useful for anyone spending huge amounts of time in the sun" +Flags = {Take,Armor} +Attributes = {Weight=850,SlotType=HEAD,ArmorValue=1} + +TypeID = 5918 +Name = "pirate knee breeches" +Description = "It is quite fashionable as well as useful for anyone spending huge amounts of time in the sun" +Flags = {Take,Armor} +Attributes = {Weight=1200,SlotType=LEGS,ArmorValue=1} + +TypeID = 5919 +Name = "a dragon claw" +Description = "It is the claw of Demodras" +Flags = {Take} +Attributes = {Weight=1250} + +TypeID = 5920 +Name = "a green dragon scale" +Flags = {Cumulative,Take} +Attributes = {Weight=80} + +TypeID = 5921 +Name = "a heaven blossom" +Flags = {Cumulative,Take} +Attributes = {Weight=210} + +TypeID = 5922 +Name = "a holy orchid" +Flags = {Cumulative,Take} +Attributes = {Weight=250} + +TypeID = 5923 +Name = "" # this is nothing in client + +TypeID = 5924 +Name = "a damaged steel helmet" +Description = "The words 'Ramsay the Reckless' are engraved inside. It appears to be cracked and broken" +Flags = {Take} +Attributes = {Weight=4600} + +TypeID = 5925 +Name = "a hardened bone" +Flags = {Cumulative,Take} +Attributes = {Weight=600} + +TypeID = 5926 +Name = "a pirate backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 5927 +Name = "a pirate bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 5928 +Name = "an empty goldfish bowl" +Flags = {Take} +Attributes = {Weight=3200} + +TypeID = 5929 +Name = "a goldfish bowl" +Flags = {Take} +Attributes = {Weight=3200} + +TypeID = 5930 +Name = "a behemoth claw" +Flags = {Cumulative,Take} +Attributes = {Weight=1250} + +TypeID = 5931 +Name = "the remains of ferumbras" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5932,TotalExpireTime=900} + +TypeID = 5932 +Name = "the remains of ferumbras" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=5933,TotalExpireTime=600} + +TypeID = 5933 +Name = "the remains of ferumbras" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5934 +Name = "a dead frog" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=1000,FluidSource=BLOOD,ExpireTarget=5935,TotalExpireTime=600} + +TypeID = 5935 +Name = "a dead frog" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=800,ExpireTarget=5936,TotalExpireTime=600} + +TypeID = 5936 +Name = "a dead frog" +Flags = {Corpse,Take,Expire} +Attributes = {Weight=500,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5937 +Name = "a botanist's container" +Description = "It holds a sample of the rare griffinclaw flower" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 5938 +Name = "Ceiron's waterskin" +Description = "It is empty" +Flags = {Take} +Attributes = {Weight=700} + +TypeID = 5939 +Name = "Ceiron's waterskin" +Description = "It contains a special sample of water from a hydra cave" +Flags = {Take} +Attributes = {Weight=700} + +TypeID = 5940 +Name = "Ceiron's wolf tooth chain" +Description = "It has the letter 'C' carved into one of the teeth" +Flags = {Take} +Attributes = {Weight=330,SlotType=NECKLACE} + +TypeID = 5941 +Name = "a wooden stake" +Description = "It is a simple wooden stake" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 5942 +Name = "a blessed wooden stake" +Description = "Many mighty priests have blessed this stake" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 5943 +Name = "Morgaroth's heart" +Flags = {Take} +Attributes = {Weight=3500} + +TypeID = 5944 +Name = "a soul orb" +Description = "This strange object seems to be made of half spirit, half metal. It is unknown to most smiths" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 5945 +Name = "a coral comb" +Description = "It once belonged to a mermaid" +Flags = {Take} +Attributes = {Weight=450} + +TypeID = 5946 +Name = "a comb" +Flags = {Take} +Attributes = {Weight=450} + +TypeID = 5947 +Name = "Elane's crossbow" +Description = "'For Elane, with Love' is engraved on it" +Flags = {Take} +Attributes = {Weight=4000} + +TypeID = 5948 +Name = "a red dragon leather" +Flags = {Cumulative,Take} +Attributes = {Weight=60} + +TypeID = 5949 +Name = "a beach backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 5950 +Name = "a beach bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 5951 +Name = "a fish tail" +Flags = {Take} +Attributes = {Weight=250} + +TypeID = 5952 +Name = "a poem scroll" +Description = "It contains a love poem, written by an unknown elven poet." +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 5953 +Name = "a raven herb" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 5954 +Name = "a demon horn" +Flags = {Cumulative,Take} +Attributes = {Weight=1000} + +TypeID = 5955 +Name = "" # this is nothing in client + +TypeID = 5956 +Name = "an old parchment" +Description = "It is covered with foreign symbols" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 5957 +Name = "a lottery ticket" +Description = "It has not been used yet" +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 5958 +Name = "a winning lottery ticket" +Description = "You were lucky! Go claim your prize at the potion store in Edron" +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 5959 +Name = "" # this is nothing in client + +TypeID = 5960 +Name = "a dead troll" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=3987,TotalExpireTime=10} + +TypeID = 5961 +Name = "a dead spider" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=6,FluidSource=SLIME,ExpireTarget=3988,TotalExpireTime=10} + +TypeID = 5962 +Name = "a dead cyclops" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=3989,TotalExpireTime=10} + +TypeID = 5963 +Name = "a slain skeleton" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=3990,TotalExpireTime=10} + +TypeID = 5964 +Name = "a dead rat" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=3994,TotalExpireTime=10} + +TypeID = 5965 +Name = "a dead human" +Flags = {Container} +Attributes = {Capacity=10} + +TypeID = 5966 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4001,TotalExpireTime=10} + +TypeID = 5967 +Name = "a dead rotworm" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4005,TotalExpireTime=10} + +TypeID = 5968 +Name = "a dead wolf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4007,TotalExpireTime=10} + +TypeID = 5969 +Name = "a dead minotaur" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4011,TotalExpireTime=10} + +TypeID = 5970 +Name = "a dead deer" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4016,TotalExpireTime=10} + +TypeID = 5971 +Name = "a dead dog" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4020,TotalExpireTime=10} + +TypeID = 5972 +Name = "a slain skeleton" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4024,TotalExpireTime=10} + +TypeID = 5973 +Name = "a dead dragon" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4025,TotalExpireTime=10} + +TypeID = 5974 +Name = "a dead spider" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4029,TotalExpireTime=10} + +TypeID = 5975 +Name = "a dead bear" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4030,TotalExpireTime=10} + +TypeID = 5976 +Name = "a slain ghoul" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4034,TotalExpireTime=10} + +TypeID = 5977 +Name = "a dead giant spider" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=SLIME,ExpireTarget=4038,TotalExpireTime=10} + +TypeID = 5978 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4041,TotalExpireTime=10} + +TypeID = 5979 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4043,TotalExpireTime=10} + +TypeID = 5980 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4045,TotalExpireTime=10} + +TypeID = 5981 +Name = "a dead minotaur" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4047,TotalExpireTime=10} + +TypeID = 5982 +Name = "a dead minotaur" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4052,TotalExpireTime=10} + +TypeID = 5983 +Name = "a dead minotaur" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4057,TotalExpireTime=10} + +TypeID = 5984 +Name = "a dead dragon" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4062,TotalExpireTime=10} + +TypeID = 5985 +Name = "a dead fire devil" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4067,TotalExpireTime=10} + +TypeID = 5986 +Name = "a dead lion" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4070,TotalExpireTime=10} + +TypeID = 5987 +Name = "a dead bear" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4074,TotalExpireTime=10} + +TypeID = 5988 +Name = "a dead scorpion" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4078,TotalExpireTime=10} + +TypeID = 5989 +Name = "a dead wasp" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4080,TotalExpireTime=10} + +TypeID = 5990 +Name = "a dead bug" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4083,TotalExpireTime=10} + +TypeID = 5991 +Name = "a dead sheep" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4086,TotalExpireTime=10} + +TypeID = 5992 +Name = "a dead beholder" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=SLIME,ExpireTarget=4089,TotalExpireTime=10} + +TypeID = 5993 +Name = "remains of a ghost" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4094,TotalExpireTime=10} + +TypeID = 5994 +Name = "a dead sheep" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4095,TotalExpireTime=10} + +TypeID = 5995 +Name = "a slain demon" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4097,TotalExpireTime=10} + +TypeID = 5996 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4101,TotalExpireTime=10} + +TypeID = 5997 +Name = "a dead wolf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4105,TotalExpireTime=10} + +TypeID = 5998 +Name = "a dead troll" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4109,TotalExpireTime=10} + +TypeID = 5999 +Name = "a dead behemoth" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4112,TotalExpireTime=10} + +TypeID = 6000 +Name = "a dead pig" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4116,TotalExpireTime=10} + +TypeID = 6001 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4119,TotalExpireTime=10} + +TypeID = 6002 +Name = "a dead goblin" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4121,TotalExpireTime=10} + +TypeID = 6003 +Name = "a dead elf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4126,TotalExpireTime=10} + +TypeID = 6004 +Name = "remains of a mummy" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4130,TotalExpireTime=10} + +TypeID = 6005 +Name = "a split stone golem" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4133,TotalExpireTime=10} + +TypeID = 6006 +Name = "a slain vampire" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4137,TotalExpireTime=10} + +TypeID = 6007 +Name = "a dead dwarf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4141,TotalExpireTime=10} + +TypeID = 6008 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4148,TotalExpireTime=10} + +TypeID = 6009 +Name = "a dead war wolf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4150,TotalExpireTime=10} + +TypeID = 6010 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4153,TotalExpireTime=10} + +TypeID = 6011 +Name = "a dead elf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4160,TotalExpireTime=10} + +TypeID = 6012 +Name = "a dead elf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4162,TotalExpireTime=10} + +TypeID = 6013 +Name = "a dead dwarf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4164,TotalExpireTime=10} + +TypeID = 6014 +Name = "a dead dwarf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4166,TotalExpireTime=10} + +TypeID = 6015 +Name = "a dead dwarf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4168,TotalExpireTime=10} + +TypeID = 6016 +Name = "a dead djinn" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4170,TotalExpireTime=10} + +TypeID = 6017 +Name = "a dead rabbit" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4173,TotalExpireTime=10} + +TypeID = 6018 +Name = "a dead troll" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4176,TotalExpireTime=10} + +TypeID = 6019 +Name = "a slain banshee" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4179,TotalExpireTime=10} + +TypeID = 6020 +Name = "a dead djinn" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4182,TotalExpireTime=10} + +TypeID = 6021 +Name = "a dead scarab" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=SLIME,ExpireTarget=4185,TotalExpireTime=10} + +TypeID = 6022 +Name = "a dead human" +Flags = {Container} +Attributes = {Capacity=10} + +TypeID = 6023 +Name = "a dead larva" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4191,TotalExpireTime=10} + +TypeID = 6024 +Name = "a dead scarab" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4194,TotalExpireTime=10} + +TypeID = 6025 +Name = "a dead pharaoh" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4197,TotalExpireTime=10} + +TypeID = 6026 +Name = "a dead hyaena" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4200,TotalExpireTime=10} + +TypeID = 6027 +Name = "a dead gargoyle" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4203,TotalExpireTime=10} + +TypeID = 6028 +Name = "a slain lich" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4206,TotalExpireTime=10} + +TypeID = 6029 +Name = "a slain crypt shambler" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4209,TotalExpireTime=10} + +TypeID = 6030 +Name = "a slain bonebeast" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4212,TotalExpireTime=10} + +TypeID = 6031 +Name = "a dead pharaoh" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4215,TotalExpireTime=10} + +TypeID = 6032 +Name = "a dead efreet" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4218,TotalExpireTime=10} + +TypeID = 6033 +Name = "a dead marid" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4221,TotalExpireTime=10} + +TypeID = 6034 +Name = "a dead badger" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4224,TotalExpireTime=10} + +TypeID = 6035 +Name = "a dead skunk" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4227,TotalExpireTime=10} + +TypeID = 6036 +Name = "a dead gazer" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4230,TotalExpireTime=10} + +TypeID = 6037 +Name = "a dead elder beholder" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4233,TotalExpireTime=10} + +TypeID = 6038 +Name = "a dead yeti" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4236,TotalExpireTime=10} + +TypeID = 6039 +Name = "a dead crab" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4318,TotalExpireTime=10} + +TypeID = 6040 +Name = "a dead lizard sentinel" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4324,TotalExpireTime=10} + +TypeID = 6041 +Name = "a dead lizard snakecharmer" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4327,TotalExpireTime=10} + +TypeID = 6042 +Name = "a dead chicken" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4330,TotalExpireTime=10} + +TypeID = 6043 +Name = "a dead kongra" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4333,TotalExpireTime=10} + +TypeID = 6044 +Name = "a dead merlkin" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4336,TotalExpireTime=10} + +TypeID = 6045 +Name = "a dead sibang" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4339,TotalExpireTime=10} + +TypeID = 6046 +Name = "a dead crocodile" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4342,TotalExpireTime=10} + +TypeID = 6047 +Name = "a dead carniphila" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4345,TotalExpireTime=10} + +TypeID = 6048 +Name = "a dead hydra" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4348,TotalExpireTime=10} + +TypeID = 6049 +Name = "a dead panda" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4351,TotalExpireTime=10} + +TypeID = 6050 +Name = "a dead centipede" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4354,TotalExpireTime=10} + +TypeID = 6051 +Name = "a dead tiger" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4357,TotalExpireTime=10} + +TypeID = 6052 +Name = "a dead elephant" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4360,TotalExpireTime=10} + +TypeID = 6053 +Name = "a dead bat" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4363,TotalExpireTime=10} + +TypeID = 6054 +Name = "a dead flamingo" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4366,TotalExpireTime=10} + +TypeID = 6055 +Name = "a dead dworc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4369,TotalExpireTime=10} + +TypeID = 6056 +Name = "a dead parrot" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4379,TotalExpireTime=10} + +TypeID = 6057 +Name = "a dead bird" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4382,TotalExpireTime=10} + +TypeID = 6058 +Name = "a dead dworc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4372,TotalExpireTime=10} + +TypeID = 6059 +Name = "a dead dworc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4375,TotalExpireTime=10} + +TypeID = 6060 +Name = "a dead tarantula" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4385,TotalExpireTime=10} + +TypeID = 6061 +Name = "a dead serpent spawn" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4388,TotalExpireTime=10} + +TypeID = 6062 +Name = "a lifeless nettle" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4391,TotalExpireTime=10} + +TypeID = 6063 +Name = "a dead quara pincher" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5521,TotalExpireTime=10} + +TypeID = 6064 +Name = "a dead quara mantassin" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5522,TotalExpireTime=10} + +TypeID = 6065 +Name = "a dead quara constrictor" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5523,TotalExpireTime=10} + +TypeID = 6066 +Name = "a dead quara hydromancer" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5524,TotalExpireTime=10} + +TypeID = 6067 +Name = "a dead quara predator" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5525,TotalExpireTime=10} + +TypeID = 6068 +Name = "demon dust" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=24,FluidSource=BLOOD,ExpireTarget=5526,TotalExpireTime=10} + +TypeID = 6069 +Name = "a dead carrion worm" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5539,TotalExpireTime=10} + +TypeID = 6070 +Name = "a slain pirate skeleton" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=5564,TotalExpireTime=10} + +TypeID = 6071 +Name = "a slain pirate ghost" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=5565,TotalExpireTime=10} + +TypeID = 6072 +Name = "a dead tortoise" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=6,FluidSource=BLOOD,ExpireTarget=5624,TotalExpireTime=10} + +TypeID = 6073 +Name = "a dead thornback tortoise" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=6,FluidSource=BLOOD,ExpireTarget=5627,TotalExpireTime=10} + +TypeID = 6074 +Name = "a dead mammoth" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=5665,TotalExpireTime=10} + +TypeID = 6075 +Name = "a dead blood crab" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5688,TotalExpireTime=10} + +TypeID = 6076 +Name = "a dead seagull" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=5727,TotalExpireTime=10} + +TypeID = 6077 +Name = "a dead toad" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5765,TotalExpireTime=10} + +TypeID = 6078 +Name = "remains of Ferumbras" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5931,TotalExpireTime=10} + +TypeID = 6079 +Name = "a dead frog" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=5934,TotalExpireTime=10} + +TypeID = 6080 +Name = "a dead human" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4240,TotalExpireTime=10} + +TypeID = 6081 +Name = "a dead human" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4247,TotalExpireTime=10} + +TypeID = 6082 +Name = "a dead human" + +TypeID = 6083 +Name = "a dead human" +Flags = {Container} +Attributes = {Capacity=10} + +TypeID = 6084 +Name = "a dead human" +Flags = {Container,Expire} +Attributes = {Capacity=10,ExpireTarget=6082,TotalExpireTime=120} + +TypeID = 6085 +Name = "a large trunk" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6086 +Name = "a faked label" +Flags = {Take} +Attributes = {Weight=10} + +TypeID = 6087 +Name = "a music sheet" +Description = "It contains the first verse of a hymn" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 6088 +Name = "a music sheet" +Description = "It contains the second verse of a hymn" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 6089 +Name = "a music sheet" +Description = "It contains the third verse of a hymn" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 6090 +Name = "a music sheet" +Description = "It contains the fourth and last verse of a hymn" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 6091 +Name = "a very noble-looking watch" +Description = "Unfortunately it seems to be broken" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 6092 +Name = "a very noble-looking watch" +Flags = {Take,Expire} +Attributes = {Weight=50,ExpireTarget=6091,TotalExpireTime=259200} + +TypeID = 6093 +Name = "a crystal ring" +Description = "The initials E.S. are engraved on it" +Flags = {Take} +Attributes = {Weight=90,SlotType=RING} + +TypeID = 6094 +Name = "a thread tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6095 +Name = "a pirate shirt" +Flags = {Take,Armor} +Attributes = {Weight=2000,SlotType=BODY,ArmorValue=3} + +TypeID = 6096 +Name = "a pirate hat" +Flags = {Take,Armor} +Attributes = {Weight=1250,SlotType=HEAD,ArmorValue=3} + +TypeID = 6097 +Name = "a hook" +Description = "It belonged once to a pirate" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 6098 +Name = "an eye patch" +Description = "It belonged once to a pirate" +Flags = {Cumulative,Take} +Attributes = {Weight=150} + +TypeID = 6099 +Name = "Brutus Bloodbeard's hat" +Flags = {Take} +Attributes = {Weight=1250} + +TypeID = 6100 +Name = "the Lethal Lissy's shirt" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 6101 +Name = "Ron the Ripper's sabre" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2500,WeaponType=SWORD,Attack=12,Defense=10} + +TypeID = 6102 +Name = "Deadeye Devious' eye patch" +Flags = {Take} +Attributes = {Weight=150} + +TypeID = 6103 +Name = "an unholy boo" +Flags = {Take} +Attributes = {Weight=150} + +TypeID = 6104 +Name = "a jewel case" +Flags = {Container,Take} +Attributes = {Capacity=1,Weight=170} + +TypeID = 6105 +Name = "Striker's favourite pillow" +Flags = {Take} +Attributes = {Weight=1700} + +TypeID = 6106 +Name = "a bottle of whisper beer" +Flags = {Take} +Attributes = {Weight=300} + +TypeID = 6107 +Name = "a staff" +Description = "It must be the one which Simon the Beggar talked about" +Flags = {Take} +Attributes = {Weight=3800} + +TypeID = 6108 +Name = "an atlas" +Description = "It is filled with detailed maps" +Flags = {Take} +Attributes = {Weight=1300} + +TypeID = 6109 +Name = "a weapon rack" +Flags = {Container,Unpass,Unlay,Rotate} +Attributes = {Capacity=20,RotateTarget=6110} + +TypeID = 6110 +Name = "a weapon rack" +Flags = {Container,Unpass,Unlay,Rotate} +Attributes = {Capacity=20,RotateTarget=6109} + +TypeID = 6111 +Name = "an armor rack" +Flags = {Container,Unpass,Unlay,Rotate} +Attributes = {Capacity=20,RotateTarget=6112} + +TypeID = 6112 +Name = "an armor rack" +Flags = {Container,Unpass,Unlay,Rotate} +Attributes = {Capacity=20,RotateTarget=6111} + +TypeID = 6113 +Name = "a letter to Eremo" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 6114 +Name = "an armor rack kit" +Description = "Use it in your house to construct an armor rack" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 6115 +Name = "a weapon rack kit" +Description = "Use it in your house to construct a weapon rack" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 6116 +Name = "electric sparks" +Flags = {Bottom,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=137} + +TypeID = 6117 +Name = "electric sparks" +Flags = {Bottom,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=137} + +TypeID = 6118 +Name = "a treasure map" +Description = "It obviously shows Treasure Island including a big, red cross" +Flags = {Unmove} + +TypeID = 6119 +Name = "a scroll" +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 6120 +Name = "Dragha's spellbook" +Description = "It apparently belonged to someone called Dragha, the apprentice of a voodoomaster" +Flags = {Take} +Attributes = {Weight=5800} + +TypeID = 6121 +Name = "a note pinned on the wall" +Flags = {Unmove} + +TypeID = 6122 +Name = "a note pinned on the wall" +Flags = {Unmove} + +TypeID = 6123 +Name = "a piano" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6124 +Name = "a damaged logbook" +Description = "It must be the one which the explorer society requested" +Flags = {Take} +Attributes = {Weight=1100} + +TypeID = 6125 +Name = "a tortoise egg from Nargor" +Description = "Handle with care and don't try to eat it" +Flags = {Take} +Attributes = {Nutrition=8,Weight=30} + +TypeID = 6126 +Name = "a peg leg" +Description = "It belonged once to a pirate" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 6127 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 6128 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 6129 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 6130 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 6131 +Name = "a tortoise shield" +Flags = {Take,Shield} +Attributes = {Weight=5200,Defense=26} + +TypeID = 6132 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6133 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6134 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6135 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6136 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6137 +Name = "a grass roof" +Flags = {Unmove} + +TypeID = 6138 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6139 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6140 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6141 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6142 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6143 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6144 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6145 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6146 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6147 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6148 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6149 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6150 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6151 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6152 +Name = "a tree wall" +Flags = {Clip,Unmove} + +TypeID = 6153 +Name = "a tree wall" +Flags = {Clip,Unmove} + +TypeID = 6154 +Name = "a tree wall" +Flags = {Clip,Unmove} + +TypeID = 6155 +Name = "a tree wall" +Flags = {Clip,Unmove} + +TypeID = 6156 +Name = "a tree wall" +Flags = {Clip,Unmove} + +TypeID = 6157 +Name = "a tree wall" +Flags = {Clip,Unmove} + +TypeID = 6158 +Name = "a tree wall" +Flags = {Clip,Unmove} + +TypeID = 6159 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6160 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6161 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6162 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6163 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6164 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6165 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6166 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6167 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6168 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6169 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6170 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6171 +Name = "a grass roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 6172 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=160} + +TypeID = 6173 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=160} + +TypeID = 6174 +Name = "a tree archway" +Flags = {Top,Unmove} + +TypeID = 6175 +Name = "a tree archway" +Flags = {Top,Unmove} + +TypeID = 6176 +Name = "a branch" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6177 +Name = "a tree hole" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6178 +Name = "a tree hole" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6179 +Name = "a tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6180 +Name = "a tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6181 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6182 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6183 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6184 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6185 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6186 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6187 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6188 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6189 +Name = "a branch" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6190 +Name = "a branch" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6191 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6192 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6193 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 6194 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6195 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6196 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 6197 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6198 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 6199 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6200 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 6201 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6202 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 6203 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6204 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 6205 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6206 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 6207 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6208 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 6209 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 6210 +Name = "a rope bridge" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 6211 +Name = "a rope bridge" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 6212 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 6213 +Name = "a rope bridge" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 6214 +Name = "a rope bridge" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 6215 +Name = "grass" +Flags = {Unmove} + +TypeID = 6216 +Name = "grass" +Flags = {Unmove} + +TypeID = 6217 +Name = "grass" +Flags = {Unmove} + +TypeID = 6218 +Name = "grass" +Flags = {Unmove} + +TypeID = 6219 +Name = "twines" +Flags = {Unmove} + +TypeID = 6220 +Name = "twines" +Flags = {Unmove} + +TypeID = 6221 +Name = "twines" +Flags = {Unmove} + +TypeID = 6222 +Name = "twines" +Flags = {Unmove} + +TypeID = 6223 +Name = "twines" +Flags = {Unmove} + +TypeID = 6224 +Name = "twines" +Flags = {Unmove} + +TypeID = 6225 +Name = "flowers" +Flags = {Unmove} + +TypeID = 6226 +Name = "flowers" +Flags = {Unmove} + +TypeID = 6227 +Name = "flowers" +Flags = {Unmove} + +TypeID = 6228 +Name = "flowers" +Flags = {Unmove} + +TypeID = 6229 +Name = "flowers" +Flags = {Unmove} + +TypeID = 6230 +Name = "flowers" +Flags = {Unmove} + +TypeID = 6231 +Name = "a tree archway" +Flags = {Top,Unmove} + +TypeID = 6232 +Name = "a tree archway" +Flags = {Top,Unmove} + +TypeID = 6233 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 6234 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 6235 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 6236 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 6237 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6238 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6239 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6240 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6241 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6242 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6243 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6244 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6245 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6246 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6247 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6248 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6249 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6250 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 6251 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6252 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6253 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 6254 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6255 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 6256 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6257 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 6258 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6259 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 6260 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6261 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 6262 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6263 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 6264 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6265 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 6266 +Name = "metal trash" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6267 +Name = "tendrils" +Flags = {Unpass,Unmove} + +TypeID = 6268 +Name = "tendrils" +Flags = {Unpass,Unmove} + +TypeID = 6269 +Name = "tendrils" +Flags = {Unpass,Unmove} + +TypeID = 6270 +Name = "some leaves" +Flags = {Unmove} + +TypeID = 6271 +Name = "some leaves" +Flags = {Unmove} + +TypeID = 6272 +Name = "some leaves" +Flags = {Unmove} + +TypeID = 6273 +Name = "some leaves" +Flags = {Unmove} + +TypeID = 6274 +Name = "a tree archway" +Flags = {Top,Unmove} + +TypeID = 6275 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 6276 +Name = "a lump of cake dough" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 6277 +Name = "a cake" +Flags = {UseEvent,Take} +Attributes = {Nutrition=10,Weight=500} + +TypeID = 6278 +Name = "a cake" +Description = "It is nicely decorated with fruits and icing" +Flags = {UseEvent,CollisionEvent,Take} +Attributes = {Nutrition=10,Weight=500} + +TypeID = 6279 +Name = "a party cake" +Description = "It is nicely decorated with fruits, icing and a candle. Someone is caring about you" +Flags = {UseEvent,Take} +Attributes = {Weight=500} + +TypeID = 6280 +Name = "a broken brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6281 +Name = "a broken brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6282 +Name = "a broken brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6283 +Name = "a broken brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6284 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6285 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6286 +Name = "" # this is nothing in client + +TypeID = 6287 +Name = "" # this is nothing in client + +TypeID = 6288 +Name = "a burning wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6289 +Name = "a burning wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6290 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6291 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6292 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6293 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6294 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6295 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6296 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6297 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6298 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6299 +Name = "a death ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=80,SlotType=RING,EquipTarget=6300} + +TypeID = 6300 +Name = "a death ring" +Description = "Wearing it makes you feel a little weaker than usual" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=80,SlotType=RING,ShieldBoost=-10,ExpireTarget=0,TotalExpireTime=480,DeEquipTarget=6299} + +TypeID = 6301 +Name = "a dead wyvern" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6302,TotalExpireTime=10} + +TypeID = 6302 +Name = "a dead wyvern" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6303,TotalExpireTime=900} + +TypeID = 6303 +Name = "a dead wyvern" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6304,TotalExpireTime=600} + +TypeID = 6304 +Name = "a dead wyvern" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6305 +Name = "a slain undead dragon" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=24,ExpireTarget=6306,TotalExpireTime=10} + +TypeID = 6306 +Name = "a slain undead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=24,ExpireTarget=6307,TotalExpireTime=900} + +TypeID = 6307 +Name = "a slain undead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=24,ExpireTarget=6308,TotalExpireTime=600} + +TypeID = 6308 +Name = "a slain undead dragon" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6309 +Name = "a slain lost soul" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=6318,TotalExpireTime=900} + +TypeID = 6310 +Name = "a slain lost soul" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=6309,TotalExpireTime=10} + +TypeID = 6311 +Name = "a slain hand of cursed fate" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6312,TotalExpireTime=10} + +TypeID = 6312 +Name = "a slain hand of cursed fate" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6313,TotalExpireTime=900} + +TypeID = 6313 +Name = "a slain hand of cursed fate" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6314,TotalExpireTime=600} + +TypeID = 6314 +Name = "a slain hand of cursed fate" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6315 +Name = "a slain betrayed wraith" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6316,TotalExpireTime=10} + +TypeID = 6316 +Name = "a slain betrayed wraith" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6317,TotalExpireTime=900} + +TypeID = 6317 +Name = "a slain betrayed wraith" +Flags = {Corpse,Expire} +Attributes = {FluidSource=BLOOD,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6318 +Name = "a slain skeleton" +Flags = {Corpse,Expire} +Attributes = {FluidSource=BLOOD,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6319 +Name = "a dead destroyer" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6320,TotalExpireTime=10} + +TypeID = 6320 +Name = "a slain destroyer" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6321,TotalExpireTime=900} + +TypeID = 6321 +Name = "a dead destroyer" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6322,TotalExpireTime=600} + +TypeID = 6322 +Name = "a dead destroyer" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6323 +Name = "elemental ashes" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6324,TotalExpireTime=10} + +TypeID = 6324 +Name = "elemental ashes" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6325,TotalExpireTime=900} + +TypeID = 6325 +Name = "elemental ashes" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6326,TotalExpireTime=600} + +TypeID = 6326 +Name = "elemental ashes" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6327 +Name = "a dead torturer" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6328,TotalExpireTime=900} + +TypeID = 6328 +Name = "a dead torturer" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6329,TotalExpireTime=600} + +TypeID = 6329 +Name = "a dead torturer" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6330 +Name = "a dead torturer" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6327,TotalExpireTime=10} + +TypeID = 6331 +Name = "a dead hellhound" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=20,FluidSource=BLOOD,ExpireTarget=6332,TotalExpireTime=10} + +TypeID = 6332 +Name = "a dead hellhound" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=20,ExpireTarget=6333,TotalExpireTime=900} + +TypeID = 6333 +Name = "a dead hellhound" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=20,ExpireTarget=6334,TotalExpireTime=600} + +TypeID = 6334 +Name = "a dead hellhound" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6335 +Name = "a dead juggernaut" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=25,FluidSource=BLOOD,ExpireTarget=6336,TotalExpireTime=10} + +TypeID = 6336 +Name = "a dead juggernaut" +Flags = {Container,Expire} +Attributes = {Capacity=25,ExpireTarget=6337,TotalExpireTime=900} + +TypeID = 6337 +Name = "a dead juggernaut" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=25,ExpireTarget=6338,TotalExpireTime=600} + +TypeID = 6338 +Name = "a dead juggernaut" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6339 +Name = "a dead nightmare" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6340,TotalExpireTime=10} + +TypeID = 6340 +Name = "a dead nightmare" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6341,TotalExpireTime=900} + +TypeID = 6341 +Name = "a dead nightmare" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6342,TotalExpireTime=600} + +TypeID = 6342 +Name = "a dead nightmare" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6343 +Name = "remains of a phantasm" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6344,TotalExpireTime=10} + +TypeID = 6344 +Name = "remains of a phantasm" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=10000,ExpireTarget=6345,TotalExpireTime=600} + +TypeID = 6345 +Name = "remains of a phantasm" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=8000,ExpireTarget=6346,TotalExpireTime=600} + +TypeID = 6346 +Name = "remains of a phantasm" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1000,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6347 +Name = "remains of a spectre" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6348,TotalExpireTime=10} + +TypeID = 6348 +Name = "remains of a spectre" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=10000,ExpireTarget=6349,TotalExpireTime=900} + +TypeID = 6349 +Name = "remains of a spectre" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=8000,ExpireTarget=6350,TotalExpireTime=600} + +TypeID = 6350 +Name = "remains of a spectre" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1000,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6351 +Name = "" # this is nothing in client + +TypeID = 6352 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6353 +Name = "a slain blightwalker" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=6354,TotalExpireTime=10} + +TypeID = 6354 +Name = "a slain blightwalker" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=10000,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6355 +Name = "an oven" +Flags = {ChangeUse,Rotate,Height} +Attributes = {ChangeTarget=6356,RotateTarget=6357,Brightness=3,LightColor=199} + +TypeID = 6356 +Name = "an oven" +Flags = {ChangeUse,Rotate,Height} +Attributes = {ChangeTarget=6355,RotateTarget=6358} + +TypeID = 6357 +Name = "an oven" +Flags = {ChangeUse,Rotate,Height} +Attributes = {ChangeTarget=6358,RotateTarget=6359,Brightness=3,LightColor=193} + +TypeID = 6358 +Name = "an oven" +Flags = {ChangeUse,Rotate,Height} +Attributes = {ChangeTarget=6357,RotateTarget=6360} + +TypeID = 6359 +Name = "an oven" +Flags = {ChangeUse,Rotate,Height} +Attributes = {ChangeTarget=6360,RotateTarget=6361,Brightness=3,LightColor=193} + +TypeID = 6360 +Name = "an oven" +Flags = {ChangeUse,Rotate,Height} +Attributes = {ChangeTarget=6359,RotateTarget=6362} + +TypeID = 6361 +Name = "an oven" +Flags = {ChangeUse,Rotate,Height} +Attributes = {ChangeTarget=6362,RotateTarget=6355,Brightness=3,LightColor=193} + +TypeID = 6362 +Name = "an oven" +Flags = {ChangeUse,Rotate,Height} +Attributes = {ChangeTarget=6361,RotateTarget=6356} + +TypeID = 6363 +Name = "a dead diabolic imp" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6364,TotalExpireTime=10} + +TypeID = 6364 +Name = "a dead diabolic imp" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6365,TotalExpireTime=900} + +TypeID = 6365 +Name = "a dead diabolic imp" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=6366,TotalExpireTime=600} + +TypeID = 6366 +Name = "a dead diabolic imp" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6367 +Name = "a bookcase" +Flags = {Container,Unpass,Rotate} +Attributes = {Capacity=6,RotateTarget=6368} + +TypeID = 6368 +Name = "a bookcase" +Flags = {Container,Unpass,Rotate} +Attributes = {Capacity=6,RotateTarget=6369} + +TypeID = 6369 +Name = "a bookcase" +Flags = {Container,Unpass,Rotate} +Attributes = {Capacity=6,RotateTarget=6370} + +TypeID = 6370 +Name = "a bookcase" +Flags = {Container,Unpass,Rotate} +Attributes = {Capacity=6,RotateTarget=6367} + +TypeID = 6371 +Name = "an oven kit" +Description = "Use it in your house to construct an oven" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 6372 +Name = "a bookcase kit" +Description = "Use it in your house to construct a bookcase" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 6373 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6374 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6375 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6376 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6377 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6378 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6379 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6380 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6381 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6382 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6383 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6384 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6385 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6386 +Name = "a greeting card" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=100} + +TypeID = 6387 +Name = "a christmas card" +Flags = {UseEvent,Take} +Attributes = {Weight=100} + +TypeID = 6388 +Name = "stony floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 6389 +Name = "an ornamented fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 6390 +Name = "a nightmare shield" +Description = "It was crafted by the ancient order of the nightmare knights" +Flags = {Take,Shield} +Attributes = {Weight=3200,Defense=37,Brightness=4,LightColor=186} + +TypeID = 6391 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 6392 +Name = "a valentine's cake" +Flags = {Take} +Attributes = {Nutrition=15,Weight=500} + +TypeID = 6393 +Name = "a cream cake" +Flags = {Take} +Attributes = {Nutrition=15,Weight=500} + +TypeID = 6394 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6395 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6396 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6397 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6398 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6399 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6400 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6401 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6402 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6403 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6404 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6405 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6406 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6407 +Name = "bones" +Flags = {Top,Unmove} + +TypeID = 6408 +Name = "bones" +Flags = {Top,Unmove} + +TypeID = 6409 +Name = "bones" +Flags = {Top,Unmove} + +TypeID = 6410 +Name = "bones" +Flags = {Top,Unmove} + +TypeID = 6411 +Name = "bones" +Flags = {Top,Unmove} + +TypeID = 6412 +Name = "a bone pillar" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6413 +Name = "bones" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6414 +Name = "bones" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6415 +Name = "bones" +Flags = {Unpass,Unmove} + +TypeID = 6416 +Name = "bones" +Flags = {Unpass,Unmove} + +TypeID = 6417 +Name = "bones" +Flags = {Unpass,Unmove} + +TypeID = 6418 +Name = "bones" +Flags = {Unpass,Unmove} + +TypeID = 6419 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6420 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6421 +Name = "bones" +Flags = {Unpass,Unmove} + +TypeID = 6422 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6423 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6424 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6425 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6426 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6427 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6428 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6429 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6430 +Name = "a branch" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6431 +Name = "a branch" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6432 +Name = "a necromancer shield" +Description = "It is enchanted with unholy, necromantic powers" +Flags = {Take,Shield} +Attributes = {Weight=3200,Defense=37,Brightness=4,LightColor=103} + +TypeID = 6433 +Name = "a blue tapestry" +Flags = {Unmove} + +TypeID = 6434 +Name = "a demon trophy" +Flags = {Unmove} + +TypeID = 6435 +Name = "a framework window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6437} + +TypeID = 6436 +Name = "a framework window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6438} + +TypeID = 6437 +Name = "a framework window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6435} + +TypeID = 6438 +Name = "a framework window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6436} + +TypeID = 6439 +Name = "a brick window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6441} + +TypeID = 6440 +Name = "a brick window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6442} + +TypeID = 6441 +Name = "a brick window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6439} + +TypeID = 6442 +Name = "a brick window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6440} + +TypeID = 6443 +Name = "a stone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6445} + +TypeID = 6444 +Name = "a stone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6446} + +TypeID = 6445 +Name = "a stone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6443} + +TypeID = 6446 +Name = "a stone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6444} + +TypeID = 6447 +Name = "a marble window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6448 +Name = "a marble window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6449 +Name = "a tree window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6451} + +TypeID = 6450 +Name = "a tree window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6452} + +TypeID = 6451 +Name = "a tree window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6449} + +TypeID = 6452 +Name = "a tree window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6450} + +TypeID = 6453 +Name = "a sandstone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6455} + +TypeID = 6454 +Name = "a sandstone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6456} + +TypeID = 6455 +Name = "a sandstone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6453} + +TypeID = 6456 +Name = "a sandstone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6454} + +TypeID = 6457 +Name = "a bamboo window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6459} + +TypeID = 6458 +Name = "a bamboo window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6460} + +TypeID = 6459 +Name = "a bamboo window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6457} + +TypeID = 6460 +Name = "a bamboo window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6458} + +TypeID = 6461 +Name = "a sandstone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6463} + +TypeID = 6462 +Name = "a sandstone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6464} + +TypeID = 6463 +Name = "a sandstone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6461} + +TypeID = 6464 +Name = "a sandstone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6462} + +TypeID = 6465 +Name = "a stone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6467} + +TypeID = 6466 +Name = "a stone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6468} + +TypeID = 6467 +Name = "a stone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6465} + +TypeID = 6468 +Name = "a stone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6466} + +TypeID = 6469 +Name = "a wooden window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6471} + +TypeID = 6470 +Name = "a wooden window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6472} + +TypeID = 6471 +Name = "a wooden window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6469} + +TypeID = 6472 +Name = "a wooden window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6470} + +TypeID = 6473 +Name = "wooden planks" +Flags = {Unmove} + +TypeID = 6474 +Name = "wooden planks" +Flags = {Unmove} + +TypeID = 6475 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6476 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6477 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6478 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6479 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6480 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6481 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6482 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6483 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6484 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6485 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6486 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6487 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6488 +Name = "a christmas branch" +Flags = {ChangeUse,Take} +Attributes = {ChangeTarget=6489,Weight=1800,Brightness=2,LightColor=206} + +TypeID = 6489 +Name = "a christmas branch" +Flags = {ChangeUse,Take} +Attributes = {ChangeTarget=6488,Weight=1800,Brightness=0,LightColor=0} + +TypeID = 6490 +Name = "a firefly" +Flags = {Top,Unmove,Expire} +Attributes = {ExpireTarget=6493,TotalExpireTime=8} + +TypeID = 6491 +Name = "a bat decoration" +Flags = {Take,Hang} +Attributes = {Weight=200} + +TypeID = 6492 +Name = "a bat decoration" +Flags = {Unmove} + +TypeID = 6493 +Name = "a firefly" +Flags = {Top,Unmove,Expire} +Attributes = {ExpireTarget=6490,TotalExpireTime=8} + +TypeID = 6494 +Name = "a firefly" +Flags = {Top,Unmove,Expire} +Attributes = {ExpireTarget=6497,TotalExpireTime=8} + +TypeID = 6495 +Name = "a bat decoration" +Flags = {Unmove} + +TypeID = 6496 +Name = "a christmas present bag" +Description = "It contains presents which were stolen from Santa. Bring them back to Ruprecht on Vega" +Flags = {Take} +Attributes = {Weight=8000} + +TypeID = 6497 +Name = "a firefly" +Flags = {Top,Unmove} + +TypeID = 6498 +Name = "a certificate" +Description = "You have mastered the Dream Challenge and may apply for joining the order of the Nightmare Knights" +Flags = {Take,Hang} +Attributes = {Weight=150} + +TypeID = 6499 +Name = "a demonic essence" +Description = "Someone might be interested in trading this" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 6500 +Name = "a gingerbreadman" +Flags = {Take} +Attributes = {Nutrition=20,Weight=500} + +TypeID = 6501 +Name = "a christmas wreath" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 6502 +Name = "a christmas garland" +Flags = {Take,Hang} +Attributes = {Weight=500} + +TypeID = 6503 +Name = "a red christmas garland" +Flags = {Take,Hang} +Attributes = {Weight=500} + +TypeID = 6504 +Name = "a blue christmas garland" +Flags = {Take,Hang} +Attributes = {Weight=500} + +TypeID = 6505 +Name = "a christmas present" +Flags = {Container,Take} +Attributes = {Capacity=5,Weight=600} + +TypeID = 6506 +Name = "a red christmas bundle" +Description = "It contains random christmas decoration" +Flags = {Take} +Attributes = {Weight=2500} + +TypeID = 6507 +Name = "a blue christmas bundle" +Description = "It contains random christmas decoration" +Flags = {Take} +Attributes = {Weight=2500} + +TypeID = 6508 +Name = "a green christmas bundle" +Description = "It contains random christmas decoration" +Flags = {Take} +Attributes = {Weight=2500} + +TypeID = 6509 +Name = "a christmas present" +Flags = {Container,Take} +Attributes = {Capacity=5,Weight=600} + +TypeID = 6510 +Name = "a christmas present" +Flags = {Container,Take} +Attributes = {Capacity=5,Weight=600} + +TypeID = 6511 +Name = "a santa doll" +Flags = {Take} +Attributes = {Weight=750} + +TypeID = 6512 +Name = "a christmas wreath" +Flags = {Unmove} + +TypeID = 6513 +Name = "a christmas garland" +Flags = {Unmove} + +TypeID = 6514 +Name = "a christmas garland" +Flags = {Unmove} + +TypeID = 6515 +Name = "a dead plaguesmith" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=18,FluidSource=BLOOD,ExpireTarget=6519,TotalExpireTime=10} + +TypeID = 6516 +Name = "a christmas wreath" +Flags = {Unmove} + +TypeID = 6517 +Name = "a christmas garland" +Flags = {Unmove} + +TypeID = 6518 +Name = "a christmas garland" +Flags = {Unmove} + +TypeID = 6519 +Name = "a dead plaguesmith" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=18,FluidSource=BLOOD,ExpireTarget=6520,TotalExpireTime=900} + +TypeID = 6520 +Name = "a dead plaguesmith" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=18,ExpireTarget=6521,TotalExpireTime=600} + +TypeID = 6521 +Name = "a dead plaguesmith" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6522 +Name = "a parchment" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 6523 +Name = "a skeleton" +Flags = {Unmove} + +TypeID = 6524 +Name = "a skeleton" +Flags = {Unmove} + +TypeID = 6525 +Name = "a skeleton decoration" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 6526 +Name = "a christmas token" +Description = "Collect enough of these to trade them for valuable prizes" +Flags = {Cumulative,Take} +Attributes = {Weight=5} + +TypeID = 6527 +Name = "the avenger" +Description = "This holy blade was forged of shattered dreams" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6400,SlotType=TWOHANDED,WeaponType=SWORD,Attack=50,Defense=38} + +TypeID = 6528 +Name = "an infernal bolt" +Flags = {Cumulative,Take,Ammo} +Attributes = {MinimumLevel=70,Weight=90,AmmoType=BOLT,Attack=43,MissileEffect=16,Fragility=100} + +TypeID = 6529 +Name = "pair of soft boots" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=800,EquipTarget=3549,SlotType=FEET} + +TypeID = 6530 +Name = "a worn soft boots" +Description = "Someone specialised in shoes might be able to repair them for you" +Flags = {Take} +Attributes = {Weight=800} + +TypeID = 6531 +Name = "a santa hat" +Flags = {Take,Armor} +Attributes = {Weight=750,SlotType=HEAD,ArmorValue=1} + +TypeID = 6532 +Name = "a dead defiler" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=SLIME,ExpireTarget=6552,TotalExpireTime=10} + +TypeID = 6533 +Name = "a book" +Flags = {Take} +Attributes = {Weight=750} + +TypeID = 6534 +Name = "the Imperor's trident" +Flags = {Take} +Attributes = {Weight=8000} + +TypeID = 6535 +Name = "the Plasmother's remains" +Flags = {Take} +Attributes = {Weight=3000} + +TypeID = 6536 +Name = "Countess Sorrow's frozen tear" +Flags = {Take} +Attributes = {Weight=1000} + +TypeID = 6537 +Name = "Mr. Punish's handcuffs" +Flags = {Take} +Attributes = {Weight=2500} + +TypeID = 6538 +Name = "a valentine's card" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=100} + +TypeID = 6539 +Name = "the Handmaiden's protector" +Flags = {Take} +Attributes = {Weight=3500} + +TypeID = 6540 +Name = "a piece of Massacre's shell" +Flags = {Take} +Attributes = {Weight=6500} + +TypeID = 6541 +Name = "a coloured egg" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=30} + +TypeID = 6542 +Name = "a coloured egg" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=30} + +TypeID = 6543 +Name = "a coloured egg" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=30} + +TypeID = 6544 +Name = "a coloured egg" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=30} + +TypeID = 6545 +Name = "a coloured egg" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=30} + +TypeID = 6546 +Name = "Dracola's eye" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 6547 +Name = "a yellow powder" +Flags = {Cumulative,Take} +Attributes = {Weight=50} + +TypeID = 6548 +Name = "a purple powder" +Flags = {Cumulative,Take} +Attributes = {Weight=750} + +TypeID = 6549 +Name = "a green djinn powder" +Description = "The magical powder will bless you with the power to convince the green djinns" +Flags = {Cumulative,Take} +Attributes = {Weight=750} + +TypeID = 6550 +Name = "a red powder" +Description = "It reeks of hatred and malice" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 6551 +Name = "a blue djinn powder" +Description = "The magical powder will bless you with the power to convince the blue djinns" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 6552 +Name = "a dead defiler" +Flags = {Corpse,Expire} +Attributes = {FluidSource=SLIME,ExpireTarget=0,TotalExpireTime=360} + +TypeID = 6553 +Name = "a ruthless axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5800,SlotType=TWOHANDED,WeaponType=AXE,Attack=49,Defense=15} + +TypeID = 6554 +Name = "" # this is nothing in client + +TypeID = 6555 +Name = "" # this is nothing in client + +TypeID = 6556 +Name = "a tic-tac-toe token" +Description = "It seems to be rather fragile" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 6557 +Name = "a tic-tac-toe token" +Description = "It seems to be rather fragile" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 6558 +Name = "concentrated demonic blood" +Description = "Shake it to create a potion" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 6559 +Name = "" # this is nothing in client + +TypeID = 6560 +Name = "a dead human" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4247,TotalExpireTime=180} + +TypeID = 6561 +Name = "a ceremonial ankh" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 6562 +Name = "a flat roof" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 6563 +Name = "a flat roof" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 6564 +Name = "a flat roof" +Flags = {Clip,Unmove} + +TypeID = 6565 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 6566 +Name = "a stuffed dragon" +Flags = {Take,Expire} +Attributes = {Weight=850,ExpireTarget=5791,TotalExpireTime=3} + +TypeID = 6567 +Name = "a santa doll" +Flags = {Take,Expire} +Attributes = {Weight=750,ExpireTarget=6511,TotalExpireTime=3} + +TypeID = 6568 +Name = "a panda teddy" +Flags = {Take,Expire} +Attributes = {Weight=600,ExpireTarget=5080,TotalExpireTime=3} + +TypeID = 6569 +Name = "a candy" +Flags = {Cumulative,Take} +Attributes = {Nutrition=1,Weight=5} + +TypeID = 6570 +Name = "a surprise bag" +Flags = {Take} +Attributes = {Weight=1000} + +TypeID = 6571 +Name = "a surprise bag" +Flags = {Take} +Attributes = {Weight=1000} + +TypeID = 6572 +Name = "a party trumpet" +Flags = {UseEvent,Take} +Attributes = {Weight=50} + +TypeID = 6573 +Name = "a party trumpet" +Flags = {Take,Expire} +Attributes = {Weight=50,ExpireTarget=6572,TotalExpireTime=3} + +TypeID = 6574 +Name = "a bar of chocolate" +Flags = {Take} +Attributes = {Nutrition=5,Weight=10} + +TypeID = 6575 +Name = "red balloons" +Flags = {Take,Hang} +Attributes = {Weight=10} + +TypeID = 6576 +Name = "a fireworks rocket" +Description = "Do not use in your backpack or while asleep. Keep away from animals or children" +Flags = {UseEvent,Take} +Attributes = {Weight=100} + +TypeID = 6577 +Name = "green balloons" +Flags = {Take,Hang} +Attributes = {Weight=10} + +TypeID = 6578 +Name = "a party hat" +Flags = {Take,Armor} +Attributes = {Weight=750,SlotType=HEAD,ArmorValue=1} + +TypeID = 6579 +Name = "a doll" +Flags = {Take} +Attributes = {Weight=750} + +TypeID = 6580 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6581 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6582 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6583 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6584 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6585 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6586 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6587 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6588 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6589 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6590 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6591 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6592 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6593 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6594 +Name = "snow" +Description = "Someone must have walked here recently" +Flags = {Bank,Unmove,Expire} +Attributes = {Waypoints=200,ExpireTarget=799,TotalExpireTime=10} + +TypeID = 6595 +Name = "snow" +Description = "Someone must have walked here recently" +Flags = {Bank,Unmove,Expire} +Attributes = {Waypoints=200,ExpireTarget=6580,TotalExpireTime=10} + +TypeID = 6596 +Name = "snow" +Description = "Someone must have walked here recently" +Flags = {Bank,Unmove,Expire} +Attributes = {Waypoints=200,ExpireTarget=6581,TotalExpireTime=10} + +TypeID = 6597 +Name = "snow" +Description = "Someone must have walked here recently" +Flags = {Bank,Unmove,Expire} +Attributes = {Waypoints=200,ExpireTarget=6582,TotalExpireTime=10} + +TypeID = 6598 +Name = "snow" +Description = "Someone must have walked here recently" +Flags = {Bank,Unmove,Expire} +Attributes = {Waypoints=200,ExpireTarget=6583,TotalExpireTime=10} + +TypeID = 6599 +Name = "snow" +Description = "Someone must have walked here recently" +Flags = {Bank,Unmove,Expire} +Attributes = {Waypoints=200,ExpireTarget=6584,TotalExpireTime=10} + +TypeID = 6600 +Name = "snow" +Description = "Someone must have walked here recently" +Flags = {Bank,Unmove,Expire} +Attributes = {Waypoints=200,ExpireTarget=6585,TotalExpireTime=10} + +TypeID = 6601 +Name = "snow" +Description = "Someone must have walked here recently" +Flags = {Bank,Unmove,Expire} +Attributes = {Waypoints=200,ExpireTarget=6586,TotalExpireTime=10} + +TypeID = 6602 +Name = "snow" +Description = "Someone must have walked here recently" +Flags = {Bank,Unmove,Expire} +Attributes = {Waypoints=200,ExpireTarget=6587,TotalExpireTime=10} + +TypeID = 6603 +Name = "snow" +Description = "Someone must have walked here recently" +Flags = {Bank,Unmove,Expire} +Attributes = {Waypoints=200,ExpireTarget=6588,TotalExpireTime=10} + +TypeID = 6604 +Name = "snow" +Description = "Someone must have walked here recently" +Flags = {Bank,Unmove,Expire} +Attributes = {Waypoints=200,ExpireTarget=6589,TotalExpireTime=10} + +TypeID = 6605 +Name = "snow" +Description = "Someone must have walked here recently" +Flags = {Bank,Unmove,Expire} +Attributes = {Waypoints=200,ExpireTarget=6590,TotalExpireTime=10} + +TypeID = 6606 +Name = "snow" +Description = "Someone must have walked here recently" +Flags = {Bank,Unmove,Expire} +Attributes = {Waypoints=200,ExpireTarget=6591,TotalExpireTime=10} + +TypeID = 6607 +Name = "snow" +Description = "Someone must have walked here recently" +Flags = {Bank,Unmove,Expire} +Attributes = {Waypoints=200,ExpireTarget=6592,TotalExpireTime=10} + +TypeID = 6608 +Name = "snow" +Description = "Someone must have walked here recently" +Flags = {Bank,Unmove,Expire} +Attributes = {Waypoints=200,ExpireTarget=6593,TotalExpireTime=10} + +TypeID = 6609 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 6610 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 6611 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 6612 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6613 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6614 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6615 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6616 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6617 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6618 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6619 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6620 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6621 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6622 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6623 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6624 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6625 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6626 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6627 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6628 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6629 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6630 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6631 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6632 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6633 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6634 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6635 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6636 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6637 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6638 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6639 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6640 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6641 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6642 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6643 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6644 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6645 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6646 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6647 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6648 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6649 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6650 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6651 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6652 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6653 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6654 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6655 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6656 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6657 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6658 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6659 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6660 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6661 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6662 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6663 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6664 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6665 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6666 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6667 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6668 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6669 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6670 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6671 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6672 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6673 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6674 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6675 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6676 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6677 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6678 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6679 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6680 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6681 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6682 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6683 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 6684 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 6685 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 6686 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 6687 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6688 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6689 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6690 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6691 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6692 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6693 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6694 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} + +TypeID = 6695 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 6696 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 6697 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 6698 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 6699 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 6700 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 6701 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 6702 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 6703 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 6704 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 6705 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 6706 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 6707 +Name = "ice" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6708 +Name = "ice" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6709 +Name = "ice" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6710 +Name = "ice" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6711 +Name = "ice" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6712 +Name = "ice" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6713 +Name = "ice" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6714 +Name = "ice" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6715 +Name = "ice" +Flags = {Unmove} + +TypeID = 6716 +Name = "ice" +Flags = {Unmove} + +TypeID = 6717 +Name = "ice" +Flags = {Unmove} + +TypeID = 6718 +Name = "ice" +Flags = {Unmove} + +TypeID = 6719 +Name = "an icy mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6720 +Name = "an icy mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6721 +Name = "an icy mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6722 +Name = "an icy mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6723 +Name = "an icy mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6724 +Name = "an icy mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6725 +Name = "an icy mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6726 +Name = "an icy mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6727 +Name = "an icy mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6728 +Name = "an ice wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6729 +Name = "an ice wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6730 +Name = "an ice wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6731 +Name = "an ice wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6732 +Name = "an ice wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6733 +Name = "an ice wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6734 +Name = "an ice wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6735 +Name = "an ice wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6736 +Name = "an ice wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6737 +Name = "an ice wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6738 +Name = "an ice wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6739 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6740 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6741 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6742 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6743 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6744 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6745 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6746 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6747 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6748 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6749 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6750 +Name = "an ice hut" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6751 +Name = "an ice hut" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6752 +Name = "an ice hut" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6753 +Name = "an ice hut" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6754 +Name = "an ice hut" +Flags = {Bottom,Unmove} + +TypeID = 6755 +Name = "an ice hut" +Flags = {Bottom,Unmove} + +TypeID = 6756 +Name = "an ice hut" +Flags = {Bottom,Unmove} + +TypeID = 6757 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 6758 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 6759 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 6760 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 6761 +Name = "an icy mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6762 +Name = "an icy mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6763 +Name = "ice" +Flags = {Top,Unmove} + +TypeID = 6764 +Name = "ice" +Flags = {Top,Unmove} + +TypeID = 6765 +Name = "ice" +Flags = {Top,Unmove} + +TypeID = 6766 +Name = "ice" +Flags = {Top,Unmove} + +TypeID = 6767 +Name = "ice" +Flags = {Top,Unmove} + +TypeID = 6768 +Name = "an icy mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6769 +Name = "an icy mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6770 +Name = "an icy mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6771 +Name = "an icy mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6772 +Name = "an icy mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6773 +Name = "a fur wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6774 +Name = "a fur wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6775 +Name = "a fur pillar" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6776 +Name = "a fur wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6777 +Name = "a fur wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6778 +Name = "a fur wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6779 +Name = "a fur wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6780 +Name = "a fur wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6781 +Name = "a fur wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6782 +Name = "a fur wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6783 +Name = "a fur wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6784 +Name = "a fur wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6785 +Name = "a fur wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6786 +Name = "a fur wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6787 +Name = "a fur wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6788 +Name = "a fur wall window" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6789 +Name = "a fur wall window" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6790 +Name = "a fur wall window" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6791 +Name = "a fur wall window" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6792 +Name = "an entrance" +Flags = {Top,Unmove} + +TypeID = 6793 +Name = "a barbarian banner" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6794 +Name = "a barbarian banner" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6795 +Name = "an entrance" +Flags = {Unpass,Unmove} + +TypeID = 6796 +Name = "an entrance" +Flags = {Top,Unmove} + +TypeID = 6797 +Name = "an entrance" +Flags = {Unpass,Unmove} + +TypeID = 6798 +Name = "an entrance" +Flags = {Top,Unmove} + +TypeID = 6799 +Name = "an entrance" +Description = "It seems tightly closed" +Flags = {Unpass,Unmove} + +TypeID = 6800 +Name = "an entrance" +Flags = {Top,Unmove} + +TypeID = 6801 +Name = "an entrance" +Description = "It seems tightly closed" +Flags = {Unpass,Unmove} + +TypeID = 6802 +Name = "an entrance" +Flags = {Top,Unmove} + +TypeID = 6803 +Name = "an entrance" +Flags = {Top,Unmove} + +TypeID = 6804 +Name = "fur" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160} + +TypeID = 6805 +Name = "fur" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160} + +TypeID = 6806 +Name = "fur" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160} + +TypeID = 6807 +Name = "fur" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160} + +TypeID = 6808 +Name = "fur" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160} + +TypeID = 6809 +Name = "fur" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160} + +TypeID = 6810 +Name = "fur" +Flags = {Unpass,Unmove} + +TypeID = 6811 +Name = "fur" +Flags = {Unpass,Unmove} + +TypeID = 6812 +Name = "fur" +Flags = {Unpass,Unmove} + +TypeID = 6813 +Name = "fur" +Flags = {Unpass,Unmove} + +TypeID = 6814 +Name = "fur" +Flags = {Unpass,Unmove} + +TypeID = 6815 +Name = "fur" +Flags = {Unpass,Unmove} + +TypeID = 6816 +Name = "fur" +Flags = {Unpass,Unmove} + +TypeID = 6817 +Name = "fur" +Flags = {Unpass,Unmove} + +TypeID = 6818 +Name = "fur" +Flags = {Unpass,Unmove} + +TypeID = 6819 +Name = "fur" +Flags = {Unpass,Unmove} + +TypeID = 6820 +Name = "fur" +Flags = {Unpass,Unmove} + +TypeID = 6821 +Name = "fur" +Flags = {Unpass,Unmove} + +TypeID = 6822 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6823 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6824 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6825 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6826 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6827 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6828 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6829 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6830 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6831 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6832 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6833 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6834 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6835 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6836 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6837 +Name = "a frozen wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6838 +Name = "snow" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 6839 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6840 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6841 +Name = "a wooden pillar" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6842 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6843 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6844 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6845 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6846 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6847 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6848 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6849 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6850 +Name = "an ornamented wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6851 +Name = "a stone base" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6852 +Name = "a stone base" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6853 +Name = "an ornamented wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6854 +Name = "a stone base" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6855 +Name = "a stone base" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6856 +Name = "a stone base" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6857 +Name = "a stone base" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6858 +Name = "a stone base" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6859 +Name = "a stone base" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6860 +Name = "a stone base" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6861 +Name = "a stone base" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6862 +Name = "a stone base" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6863 +Name = "a stone base" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6864 +Name = "a stone base" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6865 +Name = "a stone base" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6866 +Name = "a stone base" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6867 +Name = "a stone base" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6868 +Name = "" + +TypeID = 6869 +Name = "ice" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 6870 +Name = "ice" +Flags = {Unmove} + +TypeID = 6871 +Name = "ice" +Flags = {Unmove} + +TypeID = 6872 +Name = "ice" +Flags = {Unmove} + +TypeID = 6873 +Name = "ice" +Flags = {Unmove} + +TypeID = 6874 +Name = "ice" +Flags = {Unmove} + +TypeID = 6875 +Name = "ice" +Flags = {Unmove} + +TypeID = 6876 +Name = "ice" +Flags = {Unmove} + +TypeID = 6877 +Name = "ice" +Flags = {Unmove} + +TypeID = 6878 +Name = "ice" +Flags = {Unmove} + +TypeID = 6879 +Name = "ice" +Flags = {Unmove} + +TypeID = 6880 +Name = "ice" +Flags = {Unmove} + +TypeID = 6881 +Name = "ice" +Flags = {Unmove} + +TypeID = 6882 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 6883 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 6884 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 6885 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 6886 +Name = "a strut" +Flags = {Unmove} + +TypeID = 6887 +Name = "a strut" +Flags = {Unmove} + +TypeID = 6888 +Name = "a strut" +Flags = {Unmove} + +TypeID = 6889 +Name = "tusks" +Flags = {Unpass,Unmove} + +TypeID = 6890 +Name = "tusks" +Flags = {Unpass,Unmove} + +TypeID = 6891 +Name = "a closed door" +Description = "It is locked" +Flags = {Unpass,Unmove} + +TypeID = 6892 +Name = "a closed door" +Flags = {Unpass,Unmove} + +TypeID = 6893 +Name = "an open door" +Flags = {Top,Unmove} + +TypeID = 6894 +Name = "a closed door" +Flags = {Unpass,Unmove} + +TypeID = 6895 +Name = "an open door" +Flags = {Top,Unmove} + +TypeID = 6896 +Name = "a gate of expertise" +Flags = {Unpass,Unmove} + +TypeID = 6897 +Name = "a gate of expertise" +Flags = {Top,Unmove} + +TypeID = 6898 +Name = "a closed door" +Flags = {Unpass,Unmove} + +TypeID = 6899 +Name = "an open door" +Flags = {Top,Unmove} + +TypeID = 6900 +Name = "a closed door" +Description = "It is locked" +Flags = {Unpass,Unmove} + +TypeID = 6901 +Name = "a closed door" +Flags = {Unpass,Unmove} + +TypeID = 6902 +Name = "an open door" +Flags = {Top,Unmove} + +TypeID = 6903 +Name = "a closed door" +Flags = {Unpass,Unmove} + +TypeID = 6904 +Name = "an open door" +Flags = {Top,Unmove} + +TypeID = 6905 +Name = "a gate of expertise" +Flags = {Unpass,Unmove} + +TypeID = 6906 +Name = "a gate of expertise" +Flags = {Top,Unmove} + +TypeID = 6907 +Name = "a closed door" +Flags = {Unpass,Unmove} + +TypeID = 6908 +Name = "an open door" +Flags = {Top,Unmove} + +TypeID = 6909 +Name = "a ramp" +Flags = {Bottom,Unmove} + +TypeID = 6910 +Name = "a ramp" +Flags = {Bottom,Unmove} + +TypeID = 6911 +Name = "a ramp" +Flags = {Bottom,Unmove} + +TypeID = 6912 +Name = "a ramp" +Flags = {Bottom,Unmove} + +TypeID = 6913 +Name = "a ramp" +Flags = {Bottom,Unmove} + +TypeID = 6914 +Name = "a ramp" +Flags = {Bottom,Unmove} + +TypeID = 6915 +Name = "a ramp" +Flags = {Bottom,Unmove} + +TypeID = 6916 +Name = "a ramp" +Flags = {Bottom,Unmove} + +TypeID = 6917 +Name = "a ramp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 6918 +Name = "a ramp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 6919 +Name = "a ramp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 6920 +Name = "a ramp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 6921 +Name = "a ramp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 6922 +Name = "a ramp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 6923 +Name = "a ramp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 6924 +Name = "a ramp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 6925 +Name = "ice" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 6926 +Name = "ice" +Flags = {Unmove} + +TypeID = 6927 +Name = "ice" +Flags = {Unmove} + +TypeID = 6928 +Name = "ice" +Flags = {Unmove} + +TypeID = 6929 +Name = "ice" +Flags = {Unmove} + +TypeID = 6930 +Name = "ice" +Flags = {Unmove} + +TypeID = 6931 +Name = "ice" +Flags = {Unmove} + +TypeID = 6932 +Name = "ice" +Flags = {Unmove} + +TypeID = 6933 +Name = "ice" +Flags = {Unmove} + +TypeID = 6934 +Name = "ice" +Flags = {Unmove} + +TypeID = 6935 +Name = "ice" +Flags = {Unmove} + +TypeID = 6936 +Name = "ice" +Flags = {Unmove} + +TypeID = 6937 +Name = "ice" +Flags = {Unmove} + +TypeID = 6938 +Name = "" + +TypeID = 6939 +Name = "ice" +Flags = {Unmove} + +TypeID = 6940 +Name = "ice" +Flags = {Unmove} + +TypeID = 6941 +Name = "ice" +Flags = {Unmove} + +TypeID = 6942 +Name = "ice" +Flags = {Unmove} + +TypeID = 6943 +Name = "ice" +Flags = {Unmove} + +TypeID = 6944 +Name = "ice" +Flags = {Unmove} + +TypeID = 6945 +Name = "ice" +Flags = {Unmove} + +TypeID = 6946 +Name = "ice" +Flags = {Unmove} + +TypeID = 6947 +Name = "ice" +Flags = {Unmove} + +TypeID = 6948 +Name = "ice" +Flags = {Unmove} + +TypeID = 6949 +Name = "ice" +Flags = {Unmove} + +TypeID = 6950 +Name = "ice" +Flags = {Unmove} + +TypeID = 6951 +Name = "ice" +Flags = {Unmove} + +TypeID = 6952 +Name = "ice" +Flags = {Unmove} + +TypeID = 6953 +Name = "a frozen railing" +Flags = {Unpass,Unmove} + +TypeID = 6954 +Name = "a frozen railing" +Flags = {Unpass,Unmove} + +TypeID = 6955 +Name = "a frozen railing" +Flags = {Unpass,Unmove} + +TypeID = 6956 +Name = "a frozen railing" +Flags = {Unpass,Unmove} + +TypeID = 6957 +Name = "a frozen railing" +Flags = {Unpass,Unmove} + +TypeID = 6958 +Name = "a frozen railing" +Flags = {Unpass,Unmove} + +TypeID = 6959 +Name = "a frozen railing" +Flags = {Unpass,Unmove} + +TypeID = 6960 +Name = "a frozen railing" +Flags = {Top,Unmove} + +TypeID = 6961 +Name = "a frozen railing" +Flags = {Unpass,Unmove} + +TypeID = 6962 +Name = "a frozen railing" +Flags = {Top,Unmove} + +TypeID = 6963 +Name = "a frozen railing" +Flags = {Unpass,Unmove} + +TypeID = 6964 +Name = "a frozen railing" +Flags = {Unpass,Unmove} + +TypeID = 6965 +Name = "a frozen railing" +Flags = {Unpass,Unmove} + +TypeID = 6966 +Name = "icicles" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6967 +Name = "ice floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=90} + +TypeID = 6968 +Name = "an entrance" +Flags = {Top,Unmove} + +TypeID = 6969 +Name = "an entrance" +Flags = {Top,Unmove} + +TypeID = 6970 +Name = "a mammoth skull" +Flags = {Unmove} + +TypeID = 6971 +Name = "a mammoth skull" +Flags = {Unmove} + +TypeID = 6972 +Name = "a bone totem" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6973 +Name = "a mammoth totem" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6974 +Name = "a mammoth skull" +Flags = {Unmove} + +TypeID = 6975 +Name = "a mammoth skull" +Flags = {Unmove} + +TypeID = 6976 +Name = "a mammoth skull" +Flags = {Unmove} + +TypeID = 6977 +Name = "a mammoth skull" +Flags = {Unmove} + +TypeID = 6978 +Name = "mammoth fur" +Flags = {Unpass,Unmove} + +TypeID = 6979 +Name = "mammoth fur" +Flags = {Unpass,Unmove} + +TypeID = 6980 +Name = "mammoth fur" +Flags = {Unmove} + +TypeID = 6981 +Name = "a rope" +Flags = {Top,Unmove} + +TypeID = 6982 +Name = "a rope" +Flags = {Top,Unmove} + +TypeID = 6983 +Name = "a rope" +Flags = {Top,Unmove} + +TypeID = 6984 +Name = "fish" +Flags = {Unpass,Unmove} + +TypeID = 6985 +Name = "fish" +Flags = {Unpass,Unmove} + +TypeID = 6986 +Name = "a pillar" +Flags = {Top,Unmove} + +TypeID = 6987 +Name = "a wooden dragon" +Flags = {Unmove} + +TypeID = 6988 +Name = "a wooden dragon" +Flags = {Unmove} + +TypeID = 6989 +Name = "a wooden bull" +Flags = {Unmove} + +TypeID = 6990 +Name = "a wooden bull" +Flags = {Unmove} + +TypeID = 6991 +Name = "a wooden yeti" +Flags = {Unmove} + +TypeID = 6992 +Name = "a wooden yeti" +Flags = {Unmove} + +TypeID = 6993 +Name = "wooden decoration" +Flags = {Unpass,Unmove} + +TypeID = 6994 +Name = "wooden decoration" +Flags = {Unpass,Unmove} + +TypeID = 6995 +Name = "wooden decoration" +Flags = {Unpass,Unmove} + +TypeID = 6996 +Name = "wooden decoration" +Flags = {Unpass,Unmove} + +TypeID = 6997 +Name = "wooden decoration" +Flags = {Unpass,Unmove} + +TypeID = 6998 +Name = "wooden decoration" +Flags = {Unpass,Unmove} + +TypeID = 6999 +Name = "a snowy stone" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7000 +Name = "a snowy stone" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7001 +Name = "a snowy stone" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7002 +Name = "a snowy stone" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7003 +Name = "a snowy stone" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7004 +Name = "a snowy stone" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7005 +Name = "a snowy stone" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7006 +Name = "a snowy stone" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7007 +Name = "a snowy stone" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7008 +Name = "a snowy stone" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7009 +Name = "a snowy stone" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7010 +Name = "a snowy stone" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7011 +Name = "a snowy stone" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7012 +Name = "a snowy stone" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7013 +Name = "a snowy stone" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7014 +Name = "a snowy stone" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7015 +Name = "a snowy stone" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7016 +Name = "a snowy stone" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7017 +Name = "a snowy stone" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7018 +Name = "a snowy stone" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7019 +Name = "a snowy stone" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7020 +Name = "a snowy dead tree" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7021 +Name = "a snowy dead tree" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7022 +Name = "a snowy dead tree" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7023 +Name = "a snowy pine tree" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7024 +Name = "a pine" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7025 +Name = "a nordic wall window" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7026 +Name = "a nordic wall window" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7027 +Name = "a nordic wall window" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7028 +Name = "a nordic wall window" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7029 +Name = "an ice wall window" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7030 +Name = "an ice wall window" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7031 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 7032 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 7033 +Name = "a closed door" +Description = "It is locked" +Flags = {Unpass,Unmove} + +TypeID = 7034 +Name = "a closed door" +Flags = {Unpass,Unmove} + +TypeID = 7035 +Name = "an open door" +Flags = {Top,Unmove} + +TypeID = 7036 +Name = "a closed door" +Flags = {Unpass,Unmove} + +TypeID = 7037 +Name = "an open door" +Flags = {Top,Unmove} + +TypeID = 7038 +Name = "a gate of expertise" +Flags = {Unpass,Unmove} + +TypeID = 7039 +Name = "a gate of expertise" +Flags = {Top,Unmove} + +TypeID = 7040 +Name = "a closed door" +Flags = {Unpass,Unmove} + +TypeID = 7041 +Name = "an open door" +Flags = {Top,Unmove} + +TypeID = 7042 +Name = "a closed door" +Description = "It is locked" +Flags = {Unpass,Unmove} + +TypeID = 7043 +Name = "a closed door" +Flags = {Unpass,Unmove} + +TypeID = 7044 +Name = "an open door" +Flags = {Top,Unmove} + +TypeID = 7045 +Name = "a closed door" +Flags = {Unpass,Unmove} + +TypeID = 7046 +Name = "an open door" +Flags = {Top,Unmove} + +TypeID = 7047 +Name = "a gate of expertise" +Flags = {Unpass,Unmove} + +TypeID = 7048 +Name = "a gate of expertise" +Flags = {Top,Unmove} + +TypeID = 7049 +Name = "a closed door" +Flags = {Unpass,Unmove} + +TypeID = 7050 +Name = "an open door" +Flags = {Top,Unmove} + +TypeID = 7051 +Name = "an ice wall window" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7052 +Name = "an ice wall window" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7053 +Name = "a trapdoor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 7054 +Name = "a closed door" +Flags = {Unpass,Unmove} + +TypeID = 7055 +Name = "an open door" +Flags = {Top,Unmove} + +TypeID = 7056 +Name = "a closed door" +Flags = {Unpass,Unmove} + +TypeID = 7057 +Name = "an open door" +Flags = {Top,Unmove} + +TypeID = 7058 +Name = "a skull pillar" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7059 +Name = "a glowing skull pillar" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7060 +Name = "ice" +Flags = {Top,Unmove} + +TypeID = 7061 +Name = "ice" +Flags = {Top,Unmove} + +TypeID = 7062 +Name = "rock" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 7063 +Name = "rock" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 7064 +Name = "rock" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 7065 +Name = "rock" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 7066 +Name = "rock" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 7067 +Name = "rock" +Flags = {Clip,Unmove} + +TypeID = 7068 +Name = "rock" +Flags = {Clip,Unmove} + +TypeID = 7069 +Name = "rock" +Flags = {Clip,Unmove} + +TypeID = 7070 +Name = "rock" +Flags = {Clip,Unmove} + +TypeID = 7071 +Name = "rock" +Flags = {Clip,Unmove} + +TypeID = 7072 +Name = "rock" +Flags = {Clip,Unmove} + +TypeID = 7073 +Name = "rock" +Flags = {Clip,Unmove} + +TypeID = 7074 +Name = "rock" +Flags = {Clip,Unmove} + +TypeID = 7075 +Name = "dark rock" +Flags = {Clip,Unmove} + +TypeID = 7076 +Name = "dark rock" +Flags = {Clip,Unmove} + +TypeID = 7077 +Name = "dark rock" +Flags = {Clip,Unmove} + +TypeID = 7078 +Name = "dark rock" +Flags = {Clip,Unmove} + +TypeID = 7079 +Name = "dark rock" +Flags = {Clip,Unmove} + +TypeID = 7080 +Name = "dark rock" +Flags = {Clip,Unmove} + +TypeID = 7081 +Name = "dark rock" +Flags = {Clip,Unmove} + +TypeID = 7082 +Name = "dark rock" +Flags = {Clip,Unmove} + +TypeID = 7083 +Name = "dark rock" +Flags = {Clip,Unmove} + +TypeID = 7084 +Name = "dark rock" +Flags = {Clip,Unmove} + +TypeID = 7085 +Name = "dark rock" +Flags = {Clip,Unmove} + +TypeID = 7086 +Name = "dark rock" +Flags = {Clip,Unmove} + +TypeID = 7087 +Name = "dark rock" +Flags = {Clip,Unmove} + +TypeID = 7088 +Name = "dark rock" +Flags = {Clip,Unmove} + +TypeID = 7089 +Name = "dark rock" +Flags = {Clip,Unmove} + +TypeID = 7090 +Name = "dark rock" +Flags = {Clip,Unmove} + +TypeID = 7091 +Name = "a dead frost dragon" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=7092,TotalExpireTime=10} + +TypeID = 7092 +Name = "a dead frost dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=7093,TotalExpireTime=900} + +TypeID = 7093 +Name = "a dead frost dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=7094,TotalExpireTime=600} + +TypeID = 7094 +Name = "a dead frost dragon" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 7095 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7096 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7097 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7098 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7099 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7100 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7101 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7102 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7103 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7104 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7105 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7106 +Name = "an inactive geyser" +Flags = {Unpass,Unmove} + +TypeID = 7107 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7108 +Name = "a geyser" +Flags = {Unpass,Unmove} + +TypeID = 7109 +Name = "a geyser" +Flags = {Unpass,Unmove} + +TypeID = 7110 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7111 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7112 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7113 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7114 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7115 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7116 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7117 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7118 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7119 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7120 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7121 +Name = "rails" +Flags = {Clip,Unmove} + +TypeID = 7122 +Name = "rails" +Flags = {Clip,Unmove} + +TypeID = 7123 +Name = "rails" +Flags = {Clip,Unmove} + +TypeID = 7124 +Name = "rails" +Flags = {Clip,Unmove} + +TypeID = 7125 +Name = "rails" +Flags = {Clip,Unmove} + +TypeID = 7126 +Name = "rails" +Flags = {Clip,Unmove} + +TypeID = 7127 +Name = "rails" +Flags = {Clip,Unmove} + +TypeID = 7128 +Name = "rails" +Flags = {Clip,Unmove} + +TypeID = 7129 +Name = "rails" +Flags = {Clip,Unmove} + +TypeID = 7130 +Name = "rails" +Flags = {Clip,Unmove} + +TypeID = 7131 +Name = "an ore wagon" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7132 +Name = "an ore wagon" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7133 +Name = "a buffer stop" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7134 +Name = "a buffer stop" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7135 +Name = "a buffer stop" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7136 +Name = "a buffer stop" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7137 +Name = "a bloodstain" +Flags = {Unmove} + +TypeID = 7138 +Name = "a bloodstain" +Flags = {Unmove} + +TypeID = 7139 +Name = "a bloodstain" +Flags = {Unmove} + +TypeID = 7140 +Name = "a mead horn" +Description = "It is empty" +Flags = {Take} +Attributes = {Weight=1200} + +TypeID = 7141 +Name = "a mead horn" +Description = "It is filled to the rim" +Flags = {Take} +Attributes = {Weight=1200} + +TypeID = 7142 +Name = "a bucket" +Description = "It is filled with mead" +Flags = {Unpass,Unmove} + +TypeID = 7143 +Name = "a buttress" +Flags = {Top,Unmove} + +TypeID = 7144 +Name = "a buttress" +Flags = {Top,Unmove} + +TypeID = 7145 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7146 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7147 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7148 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7149 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7150 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7151 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7152 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7153 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7154 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7155 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7156 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7157 +Name = "ice" +Flags = {Clip,Unmove} + +TypeID = 7158 +Name = "a rainbow trout" +Flags = {Cumulative,Take} +Attributes = {Weight=1200} + +TypeID = 7159 +Name = "a green perch" +Flags = {Cumulative,Take} +Attributes = {Weight=800} + +TypeID = 7160 +Name = "a frozen chest" +Flags = {Unmove} + +TypeID = 7161 +Name = "a frozen chest" +Flags = {Unmove} + +TypeID = 7162 +Name = "frozen drawers" +Flags = {Unmove} + +TypeID = 7163 +Name = "a frozen chair" +Flags = {Unmove} + +TypeID = 7164 +Name = "a frozen chair" +Flags = {Unmove} + +TypeID = 7165 +Name = "a frozen chair" +Flags = {Unmove} + +TypeID = 7166 +Name = "a frozen chair" +Flags = {Unmove} + +TypeID = 7167 +Name = "a frozen pendulum clock" +Flags = {Unpass,Unmove} + +TypeID = 7168 +Name = "a frozen pendulum clock" +Flags = {Unpass,Unmove} + +TypeID = 7169 +Name = "a frozen trough" +Flags = {Unmove} + +TypeID = 7170 +Name = "a frozen trough" +Flags = {Unmove} + +TypeID = 7171 +Name = "a snowman" +Flags = {Unpass,Unmove} + +TypeID = 7172 +Name = "a snowman" +Flags = {Unpass,Unmove} + +TypeID = 7173 +Name = "a snowman" +Flags = {Unpass,Unmove} + +TypeID = 7174 +Name = "a bear" +Description = "It seems to be sleeping tightly" +Flags = {Unpass,Unmove} + +TypeID = 7175 +Name = "a bear" +Description = "It seems to have fallen into a really deep sleep" +Flags = {Unpass,Unmove,Expire} +Attributes = {ExpireTarget=7174,TotalExpireTime=10} + +TypeID = 7176 +Name = "a mammoth" +Description = "It is looking at you fiercely" +Flags = {Unpass,Unmove} + +TypeID = 7177 +Name = "a mammoth" +Description = "It seems to be pretty much knocked out" +Flags = {Unpass,Unmove,Expire} +Attributes = {ExpireTarget=7176,TotalExpireTime=10} + +TypeID = 7178 +Name = "a baby seal" +Flags = {Unpass,Unmove} + +TypeID = 7179 +Name = "" + +TypeID = 7180 +Name = "broken pottery" +Flags = {Bottom,Unmove} +Attributes = {Weight=180} + +TypeID = 7181 +Name = "a cave entrance" +Flags = {Unmove} + +TypeID = 7182 +Name = "a cave entrance" +Flags = {Unmove} + +TypeID = 7183 +Name = "a baby seal doll" +Flags = {Take} +Attributes = {Weight=850} + +TypeID = 7184 +Name = "a baby seal doll" +Flags = {Take} +Attributes = {Weight=850} + +TypeID = 7185 +Name = "a small crack in the ice" +Description = "This would be a good place to destroy the ice" +Flags = {Clip,Unmove} + +TypeID = 7186 +Name = "a big crack in the ice" +Flags = {Clip,Unpass,Unmove} + +TypeID = 7187 +Name = "a raft" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 7188 +Name = "a raft" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 7189 +Name = "a stone shelf" +Flags = {Bottom,Container,Unpass,Unmove} +Attributes = {Capacity=6} + +TypeID = 7190 +Name = "a raft" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 7191 +Name = "a raft" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 7192 +Name = "a raft" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 7193 +Name = "a raft" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 7194 +Name = "a raft" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 7195 +Name = "a raft" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 7196 +Name = "a raft" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 7197 +Name = "a raft" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 7198 +Name = "a raft" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 7199 +Name = "a raft" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 7200 +Name = "a fragile ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 7201 +Name = "a raft" +Flags = {Clip,Unmove} + +TypeID = 7202 +Name = "a raft" +Flags = {Clip,Unmove} + +TypeID = 7203 +Name = "a raft" +Flags = {Clip,Unmove} + +TypeID = 7204 +Name = "a raft" +Flags = {Clip,Unmove} + +TypeID = 7205 +Name = "a raft" +Flags = {Clip,Unmove} + +TypeID = 7206 +Name = "a raft" +Flags = {Clip,Unmove} + +TypeID = 7207 +Name = "a raft" +Flags = {Clip,Unmove} + +TypeID = 7208 +Name = "a raft" +Flags = {Clip,Unmove} + +TypeID = 7209 +Name = "a raft" +Flags = {Clip,Unmove} + +TypeID = 7210 +Name = "a raft" +Flags = {Clip,Unmove} + +TypeID = 7211 +Name = "a raft" +Flags = {Clip,Unmove} + +TypeID = 7212 +Name = "a raft" +Flags = {Clip,Unmove} + +TypeID = 7213 +Name = "a raft rudder" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7214 +Name = "a raft rudder" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7215 +Name = "a ship rudder" +Flags = {Unpass,Unmove} + +TypeID = 7216 +Name = "a ship rudder" +Flags = {Unpass,Unmove} + +TypeID = 7217 +Name = "a ship rudder" +Flags = {Unmove} + +TypeID = 7218 +Name = "a ship rudder" +Flags = {Unpass,Unmove} + +TypeID = 7219 +Name = "a ship rudder hole" +Flags = {Unmove} + +TypeID = 7220 +Name = "a ship rudder" +Flags = {Unmove} + +TypeID = 7221 +Name = "a ship rudder" +Flags = {Unpass,Unmove} + +TypeID = 7222 +Name = "a ship rudder" +Flags = {Unpass,Unmove} + +TypeID = 7223 +Name = "a ship rudder" +Flags = {Unpass,Unmove} + +TypeID = 7224 +Name = "a ship rudder" +Flags = {Unpass,Unmove} + +TypeID = 7225 +Name = "a ship rudder" +Flags = {Unpass,Unmove} + +TypeID = 7226 +Name = "a ship rudder" +Flags = {Unpass,Unmove} + +TypeID = 7227 +Name = "a ship rudder" +Flags = {Unpass,Unmove} + +TypeID = 7228 +Name = "a figurehead" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7229 +Name = "a figurehead" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7230 +Name = "a figurehead" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7231 +Name = "a figurehead" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7232 +Name = "spikes" +Flags = {Unmove} + +TypeID = 7233 +Name = "spikes" +Flags = {Unmove} + +TypeID = 7234 +Name = "a shield" +Flags = {Unmove} + +TypeID = 7235 +Name = "a shield" +Flags = {Unmove} + +TypeID = 7236 +Name = "an ice hole" +Description = "You can see the movement of fish" +Flags = {Bank,Unpass,Unmove,Expire} +Attributes = {Waypoints=110,ExpireTarget=7200,TotalExpireTime=300} + +TypeID = 7237 +Name = "an ice hole" +Flags = {Bank,Unpass,Unmove,Expire} +Attributes = {Waypoints=110,ExpireTarget=7236,TotalExpireTime=300} + +TypeID = 7238 +Name = "a shield" +Flags = {Unmove} + +TypeID = 7239 +Name = "a shield" +Flags = {Unmove} + +TypeID = 7240 +Name = "a shield" +Flags = {Unmove} + +TypeID = 7241 +Name = "a shield" +Flags = {Unmove} + +TypeID = 7242 +Name = "a resonance crystal" +Flags = {Take} +Attributes = {Weight=250} + +TypeID = 7243 +Name = "an empty jug" +Description = "It seems to be perfect for collecting ants" +Flags = {Take} +Attributes = {Weight=750} + +TypeID = 7244 +Name = "a jug" +Description = "There are a lot of ants crawling in there" +Flags = {Take} +Attributes = {Weight=750} + +TypeID = 7245 +Name = "a piece of cactus" +Description = "It has been cut from the sun adorer cactus which is said to have curing abilities" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 7246 +Name = "Ceiron's waterskin" +Flags = {Take} +Attributes = {Weight=700} + +TypeID = 7247 +Name = "easily inflammable sulphur" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 7248 +Name = "a frostbite herb" +Description = "It has been cut from a plant which grows only in very cold regions" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 7249 +Name = "a purple kiss blossom" +Description = "It has been cut from an exotic jungle flower" +Flags = {Take} +Attributes = {Weight=20} + +TypeID = 7250 +Name = "a hydra tongue" +Description = "It is a common pest plant in warmer regions" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 7251 +Name = "a mushroom spores" +Description = "They come from a giant glimmercap mushroom" +Flags = {Take} +Attributes = {Weight=1} + +TypeID = 7252 +Name = "a baby seal" +Description = "It is covered with paint" +Flags = {Unpass,Unmove} + +TypeID = 7253 +Name = "a special flask" +Description = "It contains highly intensified poison against vermin" +Flags = {Take} +Attributes = {Weight=180} + +TypeID = 7254 +Name = "a stone shelf" +Flags = {Bottom,Container,Unpass,Unmove} +Attributes = {Capacity=6} + +TypeID = 7255 +Name = "a stone shelf" +Flags = {Bottom,Container,Unpass,Unmove} +Attributes = {Capacity=6} + +TypeID = 7256 +Name = "a slain braindeath" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=7260,TotalExpireTime=10} + +TypeID = 7257 +Name = "a slain braindeath" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=7258,TotalExpireTime=900} + +TypeID = 7258 +Name = "a slain braindeath" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 7259 +Name = "a stone shelf" +Flags = {Bottom,Container,Unpass,Unmove} +Attributes = {Capacity=10} + +TypeID = 7260 +Name = "a slain braindeath" +Flags = {Container,Unmove,Expire} +Attributes = {Capacity=10,ExpireTarget=7257,TotalExpireTime=600} + +TypeID = 7261 +Name = "a frostbite herb" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7262 +Name = "a figurehead" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7263 +Name = "a figurehead" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7264 +Name = "a sled" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7265 +Name = "a sled" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7266 +Name = "a sled" +Flags = {Bottom,Unmove} + +TypeID = 7267 +Name = "a sled" +Flags = {Bottom,Unmove} + +TypeID = 7268 +Name = "a husky" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7269 +Name = "a husky" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7270 +Name = "a husky" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7271 +Name = "a husky" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7272 +Name = "a raft" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 7273 +Name = "a stone table" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7274 +Name = "a stone table" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7275 +Name = "a stone table" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7276 +Name = "a stone table" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7277 +Name = "a stone throne" +Flags = {Bottom,Unmove} + +TypeID = 7278 +Name = "a stone throne" +Flags = {Bottom,Unmove} + +TypeID = 7279 +Name = "a stone throne" +Flags = {Bottom,Unmove} + +TypeID = 7280 +Name = "a stone throne" +Flags = {Bottom,Unmove} + +TypeID = 7281 +Name = "a memory crystal" +Description = "It is able to store memories and thoughts" +Flags = {Take} +Attributes = {Weight=250} + +TypeID = 7282 +Name = "a slain ice golem" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=18,ExpireTarget=7283,TotalExpireTime=10} + +TypeID = 7283 +Name = "a slain ice golem" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=18,ExpireTarget=7284,TotalExpireTime=900} + +TypeID = 7284 +Name = "a slain ice golem" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=18,ExpireTarget=7285,TotalExpireTime=600} + +TypeID = 7285 +Name = "a slain ice golem" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 7286 +Name = "Nilsor's waterskin" +Description = "It is empty" +Flags = {Take} +Attributes = {Weight=700} + +TypeID = 7287 +Name = "" + +TypeID = 7288 +Name = "" +Flags = {Unmove} + +TypeID = 7289 +Name = "a frost charm" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 7290 +Name = "a shard" +Description = "It is a shattered part of the Frostheart Spire" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 7291 +Name = "a frozen demon" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7292 +Name = "a frozen demon" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7293 +Name = "a frozen mammoth" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7294 +Name = "a frozen mammoth" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7295 +Name = "a frozen dragon" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7296 +Name = "a frozen dragon" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7297 +Name = "a frozen behemoth" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7298 +Name = "a frozen behemoth" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7299 +Name = "a frozen plaguesmith" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7300 +Name = "a frozen plaguesmith" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7301 +Name = "a frozen claw" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7302 +Name = "a frozen claw" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 7303 +Name = "a frozen human" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7304 +Name = "a frozen human" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7305 +Name = "a frozen human" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7306 +Name = "a frozen human" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7307 +Name = "a frozen human" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7308 +Name = "a frozen human" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7309 +Name = "a frozen human" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7310 +Name = "a frozen human" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7311 +Name = "a frozen human" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7312 +Name = "a frozen human" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7313 +Name = "" + +TypeID = 7314 +Name = "a scale from a frozen dragon" +Description = "This is the proof that there were once dragon lords on Okolnir which mutated into frost dragons" +Flags = {Take} +Attributes = {Weight=90} + +TypeID = 7315 +Name = "a resonance crystal" +Description = "You have recorded beautiful ice music on it for the Explorer Society" +Flags = {Take} +Attributes = {Weight=250} + +TypeID = 7316 +Name = "a dead husky" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=7317,TotalExpireTime=10} + +TypeID = 7317 +Name = "a dead husky" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=7318,TotalExpireTime=900} + +TypeID = 7318 +Name = "a dead husky" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=7319,TotalExpireTime=600} + +TypeID = 7319 +Name = "a dead husky" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 7320 +Name = "a dead chakoya" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=8,FluidSource=BLOOD,ExpireTarget=7321,TotalExpireTime=10} + +TypeID = 7321 +Name = "a dead chakoya" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=7322,TotalExpireTime=900} + +TypeID = 7322 +Name = "a dead chakoya" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=7323,TotalExpireTime=600} + +TypeID = 7323 +Name = "a dead chakoya" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 7324 +Name = "a dead chakoya" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=7325,TotalExpireTime=10} + +TypeID = 7325 +Name = "a dead chakoya" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=7326,TotalExpireTime=900} + +TypeID = 7326 +Name = "a dead chakoya" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=7323,TotalExpireTime=600} + +TypeID = 7327 +Name = "a dead chakoya" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=7328,TotalExpireTime=10} + +TypeID = 7328 +Name = "a dead chakoya" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=7329,TotalExpireTime=900} + +TypeID = 7329 +Name = "a dead chakoya" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=8,ExpireTarget=7323,TotalExpireTime=600} + +TypeID = 7330 +Name = "a dead frost giant" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=7331,TotalExpireTime=10} + +TypeID = 7331 +Name = "a dead frost giant" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=7332,TotalExpireTime=900} + +TypeID = 7332 +Name = "a dead frost giant" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=7333,TotalExpireTime=600} + +TypeID = 7333 +Name = "a dead frost giant" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 7334 +Name = "a dead penguin" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=7335,TotalExpireTime=10} + +TypeID = 7335 +Name = "a dead penguin" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=7336,TotalExpireTime=900} + +TypeID = 7336 +Name = "a dead penguin" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,ExpireTarget=7337,TotalExpireTime=600} + +TypeID = 7337 +Name = "a dead penguin" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 7338 +Name = "a dead silver rabbit" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=7339,TotalExpireTime=10} + +TypeID = 7339 +Name = "a dead silver rabbit" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=7340,TotalExpireTime=900} + +TypeID = 7340 +Name = "a dead silver rabbit" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=7341,TotalExpireTime=600} + +TypeID = 7341 +Name = "a dead silver rabbit" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 7342 +Name = "a fur backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 7343 +Name = "a fur bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 7344 +Name = "a slain crystal spider" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=7345,TotalExpireTime=10} + +TypeID = 7345 +Name = "a slain crystal spider" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=7346,TotalExpireTime=900} + +TypeID = 7346 +Name = "a slain crystal spider" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=7347,TotalExpireTime=600} + +TypeID = 7347 +Name = "a slain crystal spider" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 7348 +Name = "stony floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 7349 +Name = "ashes" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=5} + +TypeID = 7350 +Name = "a sandstone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 7351 +Name = "black marble floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 7352 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 7353 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 7354 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 7355 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 7356 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 7357 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 7358 +Name = "a tiled floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 7359 +Name = "a flame of life" +Flags = {Expire} +Attributes = {ExpireTarget=7360,TotalExpireTime=360} + +TypeID = 7360 +Name = "a flame of life" +Flags = {Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=60} + +TypeID = 7361 +Name = "a sarcophagus" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7362 +Name = "a sarcophagus" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7363 +Name = "a piercing bolt" +Flags = {Cumulative,Take,Weapon,MultiUse} +Attributes = {Weight=80,WeaponType=AMMUNITION,Attack=33,SlotType=AMMO} + +TypeID = 7364 +Name = "a sniper arrow" +Flags = {Cumulative,Take,Weapon,MultiUse} +Attributes = {Weight=70,WeaponType=AMMUNITION,Attack=28,SlotType=AMMO} + +TypeID = 7365 +Name = "an onyx arrow" +Flags = {Cumulative,Take,Weapon,MultiUse} +Attributes = {Weight=70,WeaponType=AMMUNITION,Attack=38,SlotType=AMMO} + +TypeID = 7366 +Name = "a viper star" +Flags = {Cumulative,Take,Weapon,MultiUse} +Attributes = {Weight=200,WeaponType=DISTANCE,Attack=28} + +TypeID = 7367 +Name = "an enchanted spear" +Flags = {Cumulative,Take,Weapon,MultiUse} +Attributes = {Weight=2000,WeaponType=DISTANCE,Attack=38} + +TypeID = 7368 +Name = "an assassin star" +Flags = {Cumulative,Take,Weapon,MultiUse} +Attributes = {Weight=200,WeaponType=DISTANCE,Attack=65} + +TypeID = 7369 +Name = "a golden trophy" +Flags = {Take} +Attributes = {Weight=3500} + +TypeID = 7370 +Name = "a silver trophy" +Flags = {Take} +Attributes = {Weight=3000} + +TypeID = 7371 +Name = "a bronze trophy" +Flags = {Take} +Attributes = {Weight=2500} + +TypeID = 7372 +Name = "an ice cream cone" +Description = "It's the famous Venorean Dream flavour, but the ice cream is melting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=100,ExpireTarget=0,TotalExpireTime=120} + +TypeID = 7373 +Name = "an ice cream cone" +Description = "It's the famous velvet vanilla flavour" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 7374 +Name = "an ice cream cone" +Description = "It's the famous sweet strawberry flavour" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 7375 +Name = "an ice cream cone" +Description = "It's the famous chilly cherry flavour" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 7376 +Name = "an ice cream cone" +Description = "It's the famous mellow melon flavour" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 7377 +Name = "an ice cream cone" +Description = "It's the famous blue-barian flavour" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 7378 +Name = "a royal spear" +Flags = {Cumulative,Take,Weapon,MultiUse} +Attributes = {Weight=2500,WeaponType=DISTANCE,Attack=35} + +TypeID = 7379 +Name = "brutetamer's staff" +Description = "Barbarian women are said to use it for conjuring beasts" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=3800,WeaponType=CLUB,Attack=35,Defense=15,SlotType=TWOHANDED} + +TypeID = 7380 +Name = "a headchopper" +Description = "This axe is only granted to a greenhorn of the Svargrond arena" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=4500,WeaponType=AXE,Attack=42,Defense=20,SlotType=TWOHANDED} + +TypeID = 7381 +Name = "a mammoth whopper" +Description = "Made from a mammoth's legbones, it is one of the favoured weapons among barbarians" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=4500,WeaponType=CLUB,Attack=30,Defense=15} + +TypeID = 7382 +Name = "a demonrage sword" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=15000,WeaponType=SWORD,Attack=47,Defense=22,SlotType=TWOHANDED} + +TypeID = 7383 +Name = "a relic sword" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=4800,WeaponType=SWORD,Attack=42,Defense=24} + +TypeID = 7384 +Name = "a mystic blade" +Description = "This sword is only granted to a scrapper of the Svargrond arena" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=3500,WeaponType=SWORD,Attack=44,Defense=25} + +TypeID = 7385 +Name = "a crimson sword" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=3600,WeaponType=SWORD,Attack=28,Defense=20} + +TypeID = 7386 +Name = "a mercenary sword" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=6800,WeaponType=SWORD,Attack=43,Defense=27,SlotType=TWOHANDED} + +TypeID = 7387 +Name = "a diamond sceptre" +Description = "The beautiful jewel on the top of this sceptre is sharp enough to cut through glass" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=1500,WeaponType=CLUB,Attack=34,Defense=18} + +TypeID = 7388 +Name = "a vile axe" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=5200,WeaponType=AXE,Attack=43,Defense=19} + +TypeID = 7389 +Name = "a heroic axe" +Description = "This axe is only granted to a scrapper of the Svargrond arena" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=6100,WeaponType=AXE,Attack=44,Defense=24} + +TypeID = 7390 +Name = "the justice seeker" +Description = "This sword is only granted to a warlord of the Svargrond arena" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=5000,WeaponType=SWORD,Attack=47,Defense=24} + +TypeID = 7391 +Name = "a thaian sword" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=6100,WeaponType=SWORD,Attack=45,Defense=29,SlotType=TWOHANDED} + +TypeID = 7392 +Name = "an orcish maul" +Description = "This club is only granted to a greenhorn of the Svargrond arena" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=5400,WeaponType=CLUB,Attack=42,Defense=18,SlotType=TWOHANDED} + +TypeID = 7393 +Name = "a demon trophy" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 7394 +Name = "a wolf trophy" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 7395 +Name = "an orc trophy" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 7396 +Name = "a behemoth trophy" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 7397 +Name = "a deer trophy" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 7398 +Name = "a cyclops trophy" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 7399 +Name = "a dragon lord trophy" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 7400 +Name = "a lion trophy" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 7401 +Name = "a minotaur trophy" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 7402 +Name = "a dragon slayer" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=8200,WeaponType=SWORD,Attack=44,Defense=28,SlotType=TWOHANDED} + +TypeID = 7403 +Name = "a berserker" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=13000,WeaponType=SWORD,Attack=48,Defense=21,SlotType=TWOHANDED} + +TypeID = 7404 +Name = "an assassin dagger" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=1700,WeaponType=SWORD,Attack=40,Defense=12} + +TypeID = 7405 +Name = "a havoc blade" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=7200,WeaponType=SWORD,Attack=49,Defense=34,SlotType=TWOHANDED} + +TypeID = 7406 +Name = "a blacksteel sword" +Description = "This sword is only granted to a greenhorn of the Svargrond arena" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=5900,WeaponType=SWORD,Attack=42,Defense=22,SlotType=TWOHANDED} + +TypeID = 7407 +Name = "a haunted blade" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=3100,WeaponType=SWORD,Attack=40,Defense=12,SlotType=TWOHANDED} + +TypeID = 7408 +Name = "a wyvern fang" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=2000,WeaponType=SWORD,Attack=32,Defense=19} + +TypeID = 7409 +Name = "a northern star" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=4200,WeaponType=CLUB,Attack=42,Defense=15} + +TypeID = 7410 +Name = "a queen's sceptre" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=2300,WeaponType=CLUB,Attack=43,Defense=19} + +TypeID = 7411 +Name = "an ornamented axe" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=6150,WeaponType=AXE,Attack=42,Defense=22} + +TypeID = 7412 +Name = "a butcher's axe" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=6300,WeaponType=AXE,Attack=41,Defense=24} + +TypeID = 7413 +Name = "a titan axe" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=8100,WeaponType=AXE,Attack=43,Defense=30,SlotType=TWOHANDED} + +TypeID = 7414 +Name = "a abyss hammer" +Description = "Floating stones form the tip of this strange weapon, held in place by unspeakable magic" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=5500,WeaponType=CLUB,Attack=47,Defense=21,SlotType=TWOHANDED} + +TypeID = 7415 +Name = "a cranial basher" +Description = "This club is only granted to a scrapper of the Svargrond arena" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=7800,WeaponType=CLUB,Attack=44,Defense=20} + +TypeID = 7416 +Name = "a bloody edge" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=5200,WeaponType=SWORD,Attack=43,Defense=21} + +TypeID = 7417 +Name = "a runed sword" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=4600,WeaponType=SWORD,Attack=45,Defense=32} + +TypeID = 7418 +Name = "a nightmare blade" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=4600,WeaponType=SWORD,Attack=46,Defense=23} + +TypeID = 7419 +Name = "a dreaded cleaver" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=3800,WeaponType=AXE,Attack=40,Defense=19} + +TypeID = 7420 +Name = "a reaper's axe" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=5900,WeaponType=AXE,Attack=46,Defense=25} + +TypeID = 7421 +Name = "an onyx flail" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=2600,WeaponType=CLUB,Attack=45,Defense=18} + +TypeID = 7422 +Name = "a jade hammer" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=3200,WeaponType=CLUB,Attack=46,Defense=20} + +TypeID = 7423 +Name = "a skullcrusher" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=12000,WeaponType=CLUB,Attack=51,Defense=20,SlotType=TWOHANDED} + +TypeID = 7424 +Name = "a lunar staff" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=3800,WeaponType=CLUB,Attack=40,Defense=25,SlotType=TWOHANDED} + +TypeID = 7425 +Name = "a taurus mace" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=5100,WeaponType=CLUB,Attack=30,Defense=18,SlotType=TWOHANDED} + +TypeID = 7426 +Name = "an amber staff" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=3500,WeaponType=CLUB,Attack=43,Defense=25,SlotType=TWOHANDED} + +TypeID = 7427 +Name = "a chaos mace" +Description = "Only those who have descended to the deepest depths may wield this hammer without it cursing them" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=6300,WeaponType=CLUB,Attack=44,Defense=21,SlotType=TWOHANDED} + +TypeID = 7428 +Name = "a bonebreaker" +Description = "This weapon has to be swung in both hands and requires a lot of strength" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=12000,WeaponType=CLUB,Attack=46,Defense=15,SlotType=TWOHANDED} + +TypeID = 7429 +Name = "a blessed sceptre" +Description = "This spectre is only granted to a warlord of the Svarground arena" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=3900,WeaponType=CLUB,Attack=47,Defense=21} + +TypeID = 7430 +Name = "a dragonbone staff" +Description = "Small flames are dancing around this strange weapon" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=1800,WeaponType=CLUB,Attack=35,Defense=18} + +TypeID = 7431 +Name = "a demonbone" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=18000,WeaponType=CLUB,Attack=48,Defense=38} + +TypeID = 7432 +Name = "a furry club" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=4200,WeaponType=CLUB,Attack=31,Defense=19} + +TypeID = 7433 +Name = "a ravenwing" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=5500,WeaponType=AXE,Attack=45,Defense=22} + +TypeID = 7434 +Name = "a royal axe" +Description = "This axe is only granted to a warlord of the Svargrond arena" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=9200,WeaponType=AXE,Attack=47,Defense=25} + +TypeID = 7435 +Name = "an impaler" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=8200,WeaponType=AXE,Attack=49,Defense=25} + +TypeID = 7436 +Name = "an angelic axe" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=5600,WeaponType=AXE,Attack=44,Defense=24,SlotType=TWOHANDED} + +TypeID = 7437 +Name = "a sapphire hammer" +Description = "A beautiful gem is embedded in this weapon" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=2100,WeaponType=CLUB,Attack=37,Defense=18} + +TypeID = 7438 +Name = "an elvish bow" +Description = "This beautifully ornamented bow was made by a skilled elvish bowmaker" +Flags = {Take} +Attributes = {Weight=3900,WeaponType=DISTANCE,SlotType=TWOHANDED} + +TypeID = 7439 +Name = "a berserk potion" +Description = "Drinking this potion temporarily increases your fighting skill while decreasing your defense" +Flags = {Take} +Attributes = {Weight=290} + +TypeID = 7440 +Name = "a mastermind potion" +Description = "Drinking this potion temporarily increases your magic skill while decreasing your magic defense" +Flags = {Take} +Attributes = {Weight=290} + +TypeID = 7441 +Name = "an ice cube" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 7442 +Name = "an ice cube" +Description = "There is a rough shape of a head" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 7443 +Name = "a bullseye potion" +Description = "Drinking this potion temporarily increases your distance skill while decreasing your defense" +Flags = {Take} +Attributes = {Weight=290} + +TypeID = 7444 +Name = "an ice cube" +Description = "There is a rough shape of a head with tusks" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 7445 +Name = "an ice cube" +Description = "There is a rough shape of a mammoth" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 7446 +Name = "an ice mammoth" +Description = "This small statue has been carefully shaped from an ice cube" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 7447 +Name = "a small ice statue" +Flags = {Take} +Attributes = {Weight=1000} + +TypeID = 7448 +Name = "a small ice statue" +Flags = {Take} +Attributes = {Weight=1000} + +TypeID = 7449 +Name = "a crystal sword" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=6900,WeaponType=SWORD,Attack=35,Defense=26,SlotType=TWOHANDED} + +TypeID = 7450 +Name = "a hammer of prophecy" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=11000,WeaponType=CLUB,Attack=52,Defense=35,SlotType=TWOHANDED} + +TypeID = 7451 +Name = "a shadow sceptre" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=4100,WeaponType=CLUB,Attack=39,Defense=17} + +TypeID = 7452 +Name = "a spiked squelcher" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=6800,WeaponType=CLUB,Attack=41,Defense=21,SlotType=TWOHANDED} + +TypeID = 7453 +Name = "an executioner" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=9900,WeaponType=AXE,Attack=51,Defense=20,SlotType=TWOHANDED} + +TypeID = 7454 +Name = "a glorious axe" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=9500,WeaponType=AXE,Attack=40,Defense=23,SlotType=TWOHANDED} + +TypeID = 7455 +Name = "a mythril axe" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=5500,WeaponType=AXE,Attack=48,Defense=28} + +TypeID = 7456 +Name = "a noble axe" +Flags = {Take,Weapon,MultiUse} +Attributes = {Weight=3800,WeaponType=AXE,Attack=39,Defense=22} + +TypeID = 7457 +Name = "fur boots" +Description = "These boots keep your toes warm in even the iciest regions, but they feel kind of heavy" +Flags = {Take,Armor} +Attributes = {Weight=1200,ArmorValue=2,SlotType=FEET} + +TypeID = 7458 +Name = "a fur cap" +Description = "It feels nice and warm on your ears" +Flags = {Take,Armor} +Attributes = {Weight=1800,ArmorValue=3,SlotType=HEAD} + +TypeID = 7459 +Name = "a pair of earmuffs" +Description = "They feel nice and warm on your ears" +Flags = {Take,Armor} +Attributes = {Weight=500,ArmorValue=0,SlotType=HEAD} + +TypeID = 7460 +Name = "a norse shield" +Flags = {Take,Shield} +Attributes = {Weight=4100,Defense=28} + +TypeID = 7461 +Name = "a krimhorn helmet" +Flags = {Take,Armor} +Attributes = {Weight=5100,ArmorValue=6,SlotType=HEAD} + +TypeID = 7462 +Name = "a ragnir helmet" +Flags = {Take,Armor} +Attributes = {Weight=3100,ArmorValue=6,SlotType=HEAD} + +TypeID = 7463 +Name = "a mammoth fur cape" +Description = "It is imbued with protective magic" +Flags = {Take,Armor} +Attributes = {Weight=2000,ArmorValue=10,SlotType=BODY} + +TypeID = 7464 +Name = "mammoth fur shorts" +Description = "They offer little protection but will at least provide some comfort and warmth" +Flags = {Take,Armor} +Attributes = {Weight=150,ArmorValue=0,SlotType=LEGS} + +TypeID = 7465 +Name = "a searing fire" + +TypeID = 7466 +Name = "a searing fire" + +TypeID = 7467 +Name = "a searing fire" + +TypeID = 7468 +Name = "a searing fire" + +TypeID = 7469 +Name = "a searing fire" + +TypeID = 7470 +Name = "a searing fire" + +TypeID = 7471 +Name = "a searing fire" + +TypeID = 7472 +Name = "a searing fire" + +TypeID = 7473 +Name = "a searing fire" + +TypeID = 7474 +Name = "a sarcophagus" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 7475 +Name = "a sarcophagus" +Flags = {Bottom,Unpass,Unmove} \ No newline at end of file diff --git a/data/world800/houses.xml b/data/world800/houses.xml new file mode 100644 index 0000000..1adfc32 --- /dev/null +++ b/data/world800/houses.xml @@ -0,0 +1,933 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/world800/map-house.xml b/data/world800/map-house.xml new file mode 100644 index 0000000..0e01939 --- /dev/null +++ b/data/world800/map-house.xml @@ -0,0 +1,963 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/world800/map-spawn.xml b/data/world800/map-spawn.xml new file mode 100644 index 0000000..d8a1f67 --- /dev/null +++ b/data/world800/map-spawn.xml @@ -0,0 +1,50652 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/world800/map.otbm b/data/world800/map.otbm new file mode 100644 index 0000000..06f4c62 Binary files /dev/null and b/data/world800/map.otbm differ diff --git a/data/world800/mymap-house.xml b/data/world800/mymap-house.xml new file mode 100644 index 0000000..9d137e4 --- /dev/null +++ b/data/world800/mymap-house.xml @@ -0,0 +1,2 @@ + + diff --git a/data/world800/mymap-spawn.xml b/data/world800/mymap-spawn.xml new file mode 100644 index 0000000..2bde032 --- /dev/null +++ b/data/world800/mymap-spawn.xml @@ -0,0 +1,2 @@ + + diff --git a/data/world800/mymap.otbm b/data/world800/mymap.otbm new file mode 100644 index 0000000..b0ed9ec Binary files /dev/null and b/data/world800/mymap.otbm differ diff --git a/data/world800/spawns.xml b/data/world800/spawns.xml new file mode 100644 index 0000000..71b3bc9 --- /dev/null +++ b/data/world800/spawns.xml @@ -0,0 +1,49439 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/protocollogin.cpp b/src/protocollogin.cpp index 5a632e7..9358006 100644 --- a/src/protocollogin.cpp +++ b/src/protocollogin.cpp @@ -136,12 +136,14 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) enableXTEAEncryption(); setXTEAKey(key); + /* if (!isProtocolAllowed(version)) { std::ostringstream ss; ss << "Only clients with protocol " << getClientVersionString(g_game.getClientVersion()) << " allowed!"; disconnectClient(ss.str(), version); return; } + */ if (g_game.getGameState() == GAME_STATE_STARTUP) { disconnectClient("Gameworld is starting up. Please wait.", version); @@ -176,6 +178,7 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) return; } + uint32_t accountNumberOtClientShallow = msg.get(); if (!accountNumber || accountNumberOtClientShallow != accountNumber) { std::ostringstream ss; @@ -183,7 +186,7 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) disconnectClient(ss.str(), version); return; } - + std::string password = msg.getString(); if (password.empty()) { disconnectClient("Invalid password.", version);