diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8f6051b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ + +*.dll +*.exe diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.md b/README.md index 08ec1bf..a5e9b00 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,23 @@ -# STYLLER 2019 - OTX3 (8.60) +# STYLLER 2021 - TFS 1.4 (8.60) -Desenvolvimento de um servidor styller usando a OTX3 (projeto baseado no TFS 1.3) na versão 8.60 do tibia. +Desenvolvimento de um servidor styller usando o TFS 1.4 Nekiro's Downgrades na versão 8.60 do tibia. ### Informações do Servidor e Sistemas: - Versão: 8.60 -- Engine: [OTX3](https://github.com/mattyx14/otxserver/tree/otxserv3) nas últimas revisões. +- Engine: [TFS 1.4 Nekiro's Downgrades](https://github.com/nekiro/TFS-1.4-Downgrades/tree/8.60) nas últimas revisões. - [Cidades](https://github.com/luanluciano93/styller/wiki/CIDADES), [sistemas](https://github.com/luanluciano93/styller/wiki/SISTEMAS) e [quests](https://github.com/luanluciano93/styller/wiki/QUESTS). - [Atualizações](https://github.com/luanluciano93/styller/wiki/CHANGES). ### Dúvidas, erros, dicas e contribuições: Caso tenha dúvidas, ou queira resolver algum bug/erro, dar dicas para o projeto, ou também ajudar em sua construção, crie um [issue](https://github.com/luanluciano93/styller/issues/new) / [pull requests](https://github.com/luanluciano93/styller/pulls) pelo github ou use o [tópico do projeto](https://tibiaking.com/forums/topic/96672-otx3-860-styller-2019/) no fórum tibiaking. +### Website compatível: +Em breve ... + ### Créditos: - luanluciano93 - GOD Bon (mapa yourots) -- mattyx14 e TFS team (pela engine do servidor) +- nekiro e TFS team (pela engine do servidor) - leoloko12 (mapa styller) - [outros](https://github.com/luanluciano93/styller/wiki/COLABORADORES) diff --git a/config.lua b/config.lua deleted file mode 100644 index ee95e66..0000000 --- a/config.lua +++ /dev/null @@ -1,68 +0,0 @@ --- Combat settings --- NOTE: valid values for worldType are: "pvp", "no-pvp" and "pvp-enforced" -worldType = "pvp" -protectionLevel = 1 -killsToRedSkull = 5 -killsToBlackSkull = 8 -pzLocked = 20 * 1000 -removeChargesFromRunes = false -removeChargesFromPotions = true -removeWeaponAmmunition = false -removeWeaponCharges = false -timeToDecreaseFrags = 12 * 60 * 60 * 1000 -whiteSkullTime = 5 * 60 * 1000 -stairJumpExhaustion = 1 * 1000 - --- Connection Config --- NOTE: maxPlayers set to 0 means no limit -ip = "127.0.0.1" -loginProtocolPort = 7171 -gameProtocolPort = 7172 -statusProtocolPort = 7171 -maxPlayers = 0 -motd = "Welcome to The Styller Server!" -serverName = "Styller" - --- Version Manual -clientVersionMin = 860 -clientVersionMax = 860 -clientVersionStr = "Only clients with protocol 8.60 allowed!" - --- Houses -housePriceEachSQM = 1000 -houseRentPeriod = "weekly" - --- Map --- NOTE: set mapName WITHOUT .otbm at the end -mapName = "styller" -mapAuthor = "Styller" - --- MySQL -mysqlHost = "127.0.0.1" -mysqlUser = "root" -mysqlPass = "" -mysqlDatabase = "styller" -mysqlPort = 3306 -mysqlSock = "" - --- Misc. -emoteSpells = true -classicEquipmentSlots = false -classicAttackSpeed = false - --- Rates --- NOTE: rateExp is not used if you have enabled stages in data/XML/stages.xml -rateExp = 300 -rateSkill = 25 -rateLoot = 2 -rateMagic = 7 -rateSpawn = 1 - --- Stamina -staminaSystem = true - --- Status server information -ownerName = "luanluciano93" -ownerEmail = "luanluciano@outlook.com" -url = "https://tibiaking.com/" -location = "Mexico" diff --git a/config.lua.dist b/config.lua.dist new file mode 100644 index 0000000..8e69712 --- /dev/null +++ b/config.lua.dist @@ -0,0 +1,177 @@ +-- Configurações de combate +-- NOTA: os valores válidos para worldType são: "pvp", "no-pvp" e "pvp-enforced" +worldType = "pvp" +hotkeyAimbotEnabled = true +protectionLevel = 1 +killsToRedSkull = 5 +killsToBlackSkull = 8 +pzLocked = 20000 +removeChargesFromRunes = false +removeChargesFromPotions = true +removeWeaponAmmunition = false +removeWeaponCharges = false +timeToDecreaseFrags = 3 * 60 * 60 +whiteSkullTime = 5 * 60 +stairJumpExhaustion = 1000 +experienceByKillingPlayers = false +expFromPlayersLevelRange = 75 + +-- Configuração 8.60 nekiro +pzLockSkullAttacker = false + +-- Configuração de conexão +-- NOTA: maxPlayers definido como 0 significa sem limite +-- NOTA: allowWalkthrough (permitir passo a passo) é aplicável apenas a jogadores +ip = "127.0.0.1" +bindOnlyGlobalAddress = false +loginProtocolPort = 7171 +gameProtocolPort = 7172 +statusProtocolPort = 7171 +maxPlayers = 0 +motd = "Welcome to The Styller 2021 Server!" +onePlayerOnlinePerAccount = true +allowClones = false +allowWalkthrough = true +serverName = "Styller" +statusTimeout = 5000 +replaceKickOnLogin = true +maxPacketsPerSecond = 25 + +-- Mortes +-- NOTA: Deixe deathLosePercent como -1 se quiser usar a fórmula padrão +-- padrão da pena de morte. Para a fórmula antiga, defina-o como 10. +-- Para sem perda de habilidade / experiência, defina-o como 0. +deathLosePercent = -1 + +-- Casas +-- NOTA: defina housePriceEachSQM como -1 para desativar a funcionalidade de compra de casa dentro do jogo +-- NOTA: os valores válidos para houseRentPeriod são:: "daily", "weekly", "monthly", "yearly" +-- use qualquer outro valor para desabilitar o sistema de aluguel +housePriceEachSQM = 10000 +houseRentPeriod = "weekly" +houseOwnedByAccount = false +houseDoorShowPrice = true +onlyInvitedCanMoveHouseItems = true + +-- Uso de item +timeBetweenActions = 200 +timeBetweenExActions = 1000 + +-- Mapa +-- NOTA: defina mapName SEM .otbm no final +mapName = "styller" +mapAuthor = "Styller" + +-- Mercado (não usado na versão 8.60) +marketOfferDuration = 30 * 24 * 60 * 60 +premiumToCreateMarketOffer = true +checkExpiredMarketOffersEachMinutes = 60 +maxMarketOffersAtATimePerPlayer = 100 + +-- MySQL +mysqlHost = "127.0.0.1" +mysqlUser = "root" +mysqlPass = "3062455" +mysqlDatabase = "styller" +mysqlPort = 3307 +mysqlSock = "" + +-- Misc. +-- NOTA: classicAttackSpeed ​​definido como true faz com que os jogadores ataquem +-- constantemente de forma regular, independentemente de outras ações, como o +-- uso de itens (poções). Esta configuração pode causar alto uso da CPU com muitos +-- jogadores e potencialmente afetar o desempenho! +-- NOTA: forceMonsterTypesOnLoad carrega todos os tipos de monstro na inicialização +-- para validá-los. Você pode desativá-lo para economizar memória, se não houver erros na +-- inicialização. +allowChangeOutfit = true +freePremium = false +kickIdlePlayerAfterMinutes = 15 +maxMessageBuffer = 4 +emoteSpells = true +classicEquipmentSlots = false +classicAttackSpeed = false +showScriptsLogInConsole = false +showOnlineStatusInCharlist = false +yellMinimumLevel = 2 +yellAlwaysAllowPremium = false +minimumLevelToSendPrivate = 1 +premiumToSendPrivate = false +forceMonsterTypesOnLoad = true +cleanProtectionZones = false +luaItemDesc = false +showPlayerLogInConsole = true + +-- VIP e Depot limites +-- NOTA: você pode definir limites personalizados por grupo em data/XML/groups.xml +vipFreeLimit = 20 +vipPremiumLimit = 100 +depotFreeLimit = 2000 +depotPremiumLimit = 10000 + +-- Luz do mundo +-- NOTA: se defaultWorldLight for definido como verdadeiro, o algoritmo de luz mundial irá +-- ser tratada nas fontes. defina-o como falso para evitar conflitos se desejar +-- fazer uso da função setWorldLight(nível, cor) +defaultWorldLight = true + +-- Salvar servidor +-- NOTA: serverSaveNotifyDuration em minutos +serverSaveNotifyMessage = true +serverSaveNotifyDuration = 5 +serverSaveCleanMap = false +serverSaveClose = false +serverSaveShutdown = true + +-- Estágios de experiência +-- NOTA: para usar um multiplicador de experiência simples, defina experienceStages como "nil" +-- minlevel e multiplier são OBRIGATÓRIOS +-- maxlevel é OPCIONAL, mas é considerado infinito por padrão +-- para desabilitar estágios, crie um estágio com minlevel 1 e sem maxlevel +experienceStages = { + { minlevel = 1, maxlevel = 50, multiplier = 300 }, + { minlevel = 51, maxlevel = 100, multiplier = 150 }, + { minlevel = 101, maxlevel = 150, multiplier = 100 }, + { minlevel = 151, maxlevel = 200, multiplier = 50 }, + { minlevel = 201, maxlevel = 250, multiplier = 30 }, + { minlevel = 251, maxlevel = 300, multiplier = 20 }, + { minlevel = 301, maxlevel = 350, multiplier = 10 }, + { minlevel = 351, multiplier = 5 } +} + +-- Experiência Fixa +-- NOTA: rateExp não é usado se você habilitou os estágios acima +rateExp = 5 +rateSkill = 3 +rateLoot = 2 +rateMagic = 3 +rateSpawn = 1 + +-- Config do Monstro Voltar ao spawn +-- despawnRange é a quantidade de andares que um monstro pode ter desde sua posição de desova +-- despawnRadius é a distância de quantas peças ele pode estar de sua posição de desova +-- removeOnDespawn irá remover o monstro se for "true" ou teletransportá-lo de volta para sua posição de spawn se for "false" +-- walkToSpawnRadius é a distância permitida que o monstro vai ficar longe da posição de spawn quando deixado sem alvos, 0 para desabilitar +deSpawnRange = 2 +deSpawnRadius = 50 +removeOnDespawn = true +walkToSpawnRadius = 15 + +-- Stamina +staminaSystem = true + +-- Scripts +warnUnsafeScripts = true +convertUnsafeScripts = true + +-- Iniciação do servidor +-- NOTA: defaultPriority só funciona no Windows e define o processo +-- prioridade, os valores válidos são: "normal", "above-normal", "high" +defaultPriority = "high" +startupDatabaseOptimization = false + +-- Informações do status do servidor +ownerName = "luanluciano93" +ownerEmail = "luanluciano@outlook.com" +url = "https://tibiaking.com/" +location = "Brazil" diff --git a/data/XML/groups.xml b/data/XML/groups.xml index cb8798c..935cb2c 100644 --- a/data/XML/groups.xml +++ b/data/XML/groups.xml @@ -1,8 +1,147 @@ - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/XML/stages.xml b/data/XML/stages.xml index 2db41e3..06b639c 100644 --- a/data/XML/stages.xml +++ b/data/XML/stages.xml @@ -1,12 +1,9 @@ - - - - - - - - - + + + + + + diff --git a/data/XML/vocations.xml b/data/XML/vocations.xml index 7df5553..a96220d 100644 --- a/data/XML/vocations.xml +++ b/data/XML/vocations.xml @@ -1,6 +1,6 @@ - + @@ -10,7 +10,7 @@ - + @@ -20,7 +20,7 @@ - + @@ -30,7 +30,7 @@ - + @@ -50,7 +50,7 @@ - + @@ -60,7 +60,7 @@ - + @@ -70,7 +70,7 @@ - + diff --git a/data/actions/actions.xml b/data/actions/actions.xml index 7c78604..bb38717 100644 --- a/data/actions/actions.xml +++ b/data/actions/actions.xml @@ -4,99 +4,96 @@ - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + - - - + + + @@ -105,72 +102,55 @@ - - - - - - - - - - - - - - - + + + + - + - - - - + + + + + + + + + - - - + + + - - - - - + - - - - - - - - - - + + + @@ -202,36 +182,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -240,22 +191,31 @@ - - - - - - - - - - + + + + + + + + + + + + + + + + + - + + + @@ -264,47 +224,43 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/actions/lib/actions.lua b/data/actions/lib/actions.lua index c535413..21ba19d 100644 --- a/data/actions/lib/actions.lua +++ b/data/actions/lib/actions.lua @@ -1,34 +1,22 @@ -local holeId = { +local wildGrowth = {1499, 11099} -- wild growth destroyable by machete +local jungleGrass = { -- grass destroyable by machete + [2782] = 2781, + [3985] = 3984, + [19433] = 19431 +} +local groundIds = {354, 355} -- pick usable ground +local sandIds = {231, 9059} -- desert sand +local holeId = { -- usable rope holes, for rope spots see global.lua 294, 369, 370, 383, 392, 408, 409, 410, 427, 428, 429, 430, 462, 469, 470, 482, - 484, 485, 489, 924, 1369, 3111, 3135, 3136, 4835, 4837, 7933, 7938, 8170, 8286, - 8285, 8284, 8281, 8280, 8279, 8277, 8276, 8567, 8585, 8596, 8595, 8249, 8250, 8251, - 8323, 8252, 8253, 8254, 8255, 8256, 8592, 8972, 9606, 9625 + 484, 485, 489, 924, 1369, 3135, 3136, 4835, 4837, 7933, 7938, 8170, 8249, 8250, + 8251, 8252, 8254, 8255, 8256, 8276, 8277, 8279, 8281, 8284, 8285, 8286, 8323, + 8567, 8585, 8595, 8596, 8972, 9606, 9625, 13190, 14461, 19519, 21536, 23713, + 26020 } +local holes = {468, 481, 483, 23712} -- holes opened by shovel +local fruits = {2673, 2674, 2675, 2676, 2677, 2678, 2679, 2680, 2681, 2682, 2684, 2685, 5097, 8839, 8840, 8841} -- fruits to make decorated cake with knife -local holes = {468, 481, 483, 7932} - -local groundIds = {354, 355} - -local others = {7932} - -local JUNGLE_GRASS = {2782, 3985} -local WILD_GROWTH = {1446, 1447, 1499, 1775, 2101, 11099} - -local function revertItem(position, itemId, transformId) - local item = Tile(position):getItemById(itemId) - if item then - item:transform(transformId) - end -end - -local function removeRemains(toPosition) - local item = Tile(toPosition):getItemById(2248) - if item then - item:remove() - end -end - -function destroyItem(player, item, fromPosition, target, toPosition, isHotkey) +function destroyItem(player, target, toPosition) if type(target) ~= "userdata" or not target:isItem() then return false end @@ -70,36 +58,123 @@ function destroyItem(player, item, fromPosition, target, toPosition, isHotkey) return true end +function onUseMachete(player, item, fromPosition, target, toPosition, isHotkey) + local targetId = target.itemid + if not targetId then + return true + end + + if table.contains(wildGrowth, targetId) then + toPosition:sendMagicEffect(CONST_ME_POFF) + target:remove() + return true + end + + local grass = jungleGrass[targetId] + if grass then + target:transform(grass) + target:decay() + player:addAchievementProgress("Nothing Can Stop Me", 100) + return true + end + + return destroyItem(player, target, toPosition) +end + +function onUsePick(player, item, fromPosition, target, toPosition, isHotkey) + if target.itemid == 11227 then -- shiny stone refining + local chance = math.random(1, 100) + if chance == 1 then + player:addItem(ITEM_CRYSTAL_COIN) -- 1% chance of getting crystal coin + elseif chance <= 6 then + player:addItem(ITEM_GOLD_COIN) -- 5% chance of getting gold coin + elseif chance <= 51 then + player:addItem(ITEM_PLATINUM_COIN) -- 45% chance of getting platinum coin + else + player:addItem(2145) -- 49% chance of getting small diamond + end + player:addAchievementProgress("Petrologist", 100) + target:getPosition():sendMagicEffect(CONST_ME_BLOCKHIT) + target:remove(1) + return true + end + + local tile = Tile(toPosition) + if not tile then + return false + end + + local ground = tile:getGround() + if not ground then + return false + end + + if table.contains(groundIds, ground.itemid) and ground.actionid == actionIds.pickHole then + ground:transform(392) + ground:decay() + toPosition:sendMagicEffect(CONST_ME_POFF) + + toPosition.z = toPosition.z + 1 + tile:relocateTo(toPosition) + return true + end + + -- Ice fishing hole + if ground.itemid == 7200 then + ground:transform(7236) + ground:decay() + toPosition:sendMagicEffect(CONST_ME_HITAREA) + return true + end + + return false +end + function onUseRope(player, item, fromPosition, target, toPosition, isHotkey) local tile = Tile(toPosition) if not tile then return false end - if table.contains(ropeSpots, tile:getGround():getId()) or tile:getItemById(14435) then - if Tile(toPosition:moveUpstairs()):hasFlag(TILESTATE_PROTECTIONZONE) and player:isPzLocked() then + local ground = tile:getGround() + + if ground and table.contains(ropeSpots, ground:getId()) or tile:getItemById(14435) then + tile = Tile(toPosition:moveUpstairs()) + if not tile then + return false + end + + if tile:hasFlag(TILESTATE_PROTECTIONZONE) and player:isPzLocked() then player:sendCancelMessage(RETURNVALUE_PLAYERISPZLOCKED) return true end + player:teleportTo(toPosition, false) return true - elseif table.contains(holeId, target.itemid) then + end + + if table.contains(holeId, target.itemid) then toPosition.z = toPosition.z + 1 tile = Tile(toPosition) - if tile then - local thing = tile:getTopVisibleThing() - if thing:isPlayer() then - if Tile(toPosition:moveUpstairs()):hasFlag(TILESTATE_PROTECTIONZONE) and thing:isPzLocked() then - return false - end - return thing:teleportTo(toPosition, false) - end - if thing:isItem() and thing:getType():isMovable() then - return thing:moveTo(toPosition:moveUpstairs()) + if not tile then + return false + end + + local thing = tile:getTopVisibleThing() + if not thing then + return true + end + + if thing:isPlayer() then + if Tile(toPosition:moveUpstairs()):queryAdd(thing) ~= RETURNVALUE_NOERROR then + return false end + + return thing:teleportTo(toPosition, false) + elseif thing:isItem() and thing:getType():isMovable() then + return thing:moveTo(toPosition:moveUpstairs()) end - player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) return true end @@ -107,18 +182,6 @@ function onUseRope(player, item, fromPosition, target, toPosition, isHotkey) end function onUseShovel(player, item, fromPosition, target, toPosition, isHotkey) - local targetId = target.itemid, target.actionid - if table.contains(others, targetId) then - target:transform(targetId + 1) - target:decay() - - return true - end - - if toPosition.x == CONTAINER_POSITION then - return false - end - local tile = Tile(toPosition) if not tile then return false @@ -136,10 +199,35 @@ function onUseShovel(player, item, fromPosition, target, toPosition, isHotkey) toPosition.z = toPosition.z + 1 tile:relocateTo(toPosition) - elseif groundId == 231 then + player:addAchievementProgress("The Undertaker", 500) + elseif target.itemid == 7932 then -- large hole + target:transform(7933) + target:decay() + player:addAchievementProgress("The Undertaker", 500) + elseif target.itemid == 20230 then -- swamp digging + if (player:getStorageValue(PlayerStorageKeys.swampDigging)) <= os.time() then + local chance = math.random(100) + if chance >= 1 and chance <= 42 then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You dug up a dead snake.") + player:addItem(3077) + elseif chance >= 43 and chance <= 79 then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You dug up a small diamond.") + player:addItem(2145) + elseif chance >= 80 then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You dug up a leech.") + player:addItem(20138) + end + player:setStorageValue(PlayerStorageKeys.swampDigging, os.time() + 7 * 24 * 60 * 60) + player:getPosition():sendMagicEffect(CONST_ME_GREEN_RINGS) + end + elseif table.contains(sandIds, groundId) then local randomValue = math.random(1, 100) - if randomValue == 1 then + if target.actionid == actionIds.sandHole and randomValue <= 20 then + ground:transform(489) + ground:decay() + elseif randomValue == 1 then Game.createItem(2159, 1, toPosition) + player:addAchievementProgress("Gold Digger", 100) elseif randomValue > 95 then Game.createMonster("Scarab", toPosition) end @@ -151,84 +239,47 @@ function onUseShovel(player, item, fromPosition, target, toPosition, isHotkey) return true end -function onUsePick(player, item, fromPosition, target, toPosition, isHotkey) - if toPosition.x == CONTAINER_POSITION then +function onUseScythe(player, item, fromPosition, target, toPosition, isHotkey) + if not table.contains({2550, 10513}, item.itemid) then return false end - if target.itemid == 11227 then -- shiny stone refining - local chance = math.random(1, 100) - if chance == 1 then - player:addItem(ITEM_CRYSTAL_COIN) -- 1% chance of getting crystal coin - elseif chance <= 6 then - player:addItem(ITEM_GOLD_COIN) -- 5% chance of getting gold coin - elseif chance <= 51 then - player:addItem(ITEM_PLATINUM_COIN) -- 45% chance of getting platinum coin - else - player:addItem(2145) -- 49% chance of getting small diamond - end - target:getPosition():sendMagicEffect(CONST_ME_BLOCKHIT) - target:remove(1) + if target.itemid == 2739 then -- wheat + target:transform(2737) + target:decay() + Game.createItem(2694, 1, toPosition) -- bunch of wheat + player:addAchievementProgress("Happy Farmer", 200) return true end - - local tile = Tile(toPosition) - if not tile then - return false - end - - local ground = tile:getGround() - if not ground then - return false - end - - if (ground.uid > 65535 or ground.actionid == 0) and not table.contains(groundIds, ground.itemid) then - return false - end - - ground:transform(392) - ground:decay() - - toPosition.z = toPosition.z + 1 - tile:relocateTo(toPosition) - return true -end - -function onUseMachete(player, item, fromPosition, target, toPosition, isHotkey) - local targetId = target.itemid - if table.contains(JUNGLE_GRASS, targetId) then - target:transform(targetId == 19433 and 19431 or targetId - 1) + if target.itemid == 5465 then -- burning sugar cane + target:transform(5464) target:decay() + Game.createItem(5467, 1, toPosition) -- bunch of sugar cane + player:addAchievementProgress("Natural Sweetener", 50) return true end + return destroyItem(player, target, toPosition) +end - if table.contains(WILD_GROWTH, targetId) then - toPosition:sendMagicEffect(CONST_ME_POFF) - target:remove() - return true +function onUseCrowbar(player, item, fromPosition, target, toPosition, isHotkey) + if not table.contains({2416, 10515}, item.itemid) then + return false end - return destroyItem(player, item, fromPosition, target, toPosition, isHotkey) + return destroyItem(player, target, toPosition) end -function onUseScythe(player, item, fromPosition, target, toPosition, isHotkey) - if not table.contains({2550, 10513}, item.itemid) then +function onUseKitchenKnife(player, item, fromPosition, target, toPosition, isHotkey) + if not table.contains({2566, 10511, 10515}, item.itemid) then return false end - if target.itemid == 5464 then - target:transform(5463) - target:decay() - Game.createItem(5467, 1, toPosition) - return true - end - - if target.itemid == 2739 then - target:transform(2737) - target:decay() - Game.createItem(2694, 1, toPosition) + if table.contains(fruits, target.itemid) and player:removeItem(6278, 1) then + target:remove(1) + player:addItem(6279, 1) + player:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) return true end - return destroyItem(player, item, fromPosition, target, toPosition, isHotkey) + return false end diff --git a/data/actions/scripts/quests/annihilator/annihilator_lever.lua b/data/actions/scripts/custom/quests/annihilator/annihilator_lever.lua similarity index 100% rename from data/actions/scripts/quests/annihilator/annihilator_lever.lua rename to data/actions/scripts/custom/quests/annihilator/annihilator_lever.lua diff --git a/data/actions/scripts/quests/annihilator/annihilator_reward.lua b/data/actions/scripts/custom/quests/annihilator/annihilator_reward.lua similarity index 100% rename from data/actions/scripts/quests/annihilator/annihilator_reward.lua rename to data/actions/scripts/custom/quests/annihilator/annihilator_reward.lua diff --git a/data/actions/scripts/quests/big_quest.lua b/data/actions/scripts/custom/quests/big_quest.lua similarity index 100% rename from data/actions/scripts/quests/big_quest.lua rename to data/actions/scripts/custom/quests/big_quest.lua diff --git a/data/actions/scripts/quests/daily_quest.lua b/data/actions/scripts/custom/quests/daily_quest.lua similarity index 100% rename from data/actions/scripts/quests/daily_quest.lua rename to data/actions/scripts/custom/quests/daily_quest.lua diff --git a/data/actions/scripts/quests/demon helmet/demonhelmet_lever.lua b/data/actions/scripts/custom/quests/demon helmet/demonhelmet_lever.lua similarity index 100% rename from data/actions/scripts/quests/demon helmet/demonhelmet_lever.lua rename to data/actions/scripts/custom/quests/demon helmet/demonhelmet_lever.lua diff --git a/data/actions/scripts/quests/demon helmet/demonhelmet_reward.lua b/data/actions/scripts/custom/quests/demon helmet/demonhelmet_reward.lua similarity index 100% rename from data/actions/scripts/quests/demon helmet/demonhelmet_reward.lua rename to data/actions/scripts/custom/quests/demon helmet/demonhelmet_reward.lua diff --git a/data/actions/scripts/quests/demon oak/demonOak.lua b/data/actions/scripts/custom/quests/demon oak/demonOak.lua similarity index 100% rename from data/actions/scripts/quests/demon oak/demonOak.lua rename to data/actions/scripts/custom/quests/demon oak/demonOak.lua diff --git a/data/actions/scripts/quests/demon oak/demonOakChest.lua b/data/actions/scripts/custom/quests/demon oak/demonOakChest.lua similarity index 100% rename from data/actions/scripts/quests/demon oak/demonOakChest.lua rename to data/actions/scripts/custom/quests/demon oak/demonOakChest.lua diff --git a/data/actions/scripts/quests/demon oak/demonOakGravestone.lua b/data/actions/scripts/custom/quests/demon oak/demonOakGravestone.lua similarity index 100% rename from data/actions/scripts/quests/demon oak/demonOakGravestone.lua rename to data/actions/scripts/custom/quests/demon oak/demonOakGravestone.lua diff --git a/data/actions/scripts/quests/inquisition_reward.lua b/data/actions/scripts/custom/quests/inquisition_reward.lua similarity index 100% rename from data/actions/scripts/quests/inquisition_reward.lua rename to data/actions/scripts/custom/quests/inquisition_reward.lua diff --git a/data/actions/scripts/quests/pits of inferno/poi_reward.lua b/data/actions/scripts/custom/quests/pits of inferno/poi_reward.lua similarity index 100% rename from data/actions/scripts/quests/pits of inferno/poi_reward.lua rename to data/actions/scripts/custom/quests/pits of inferno/poi_reward.lua diff --git a/data/actions/scripts/quests/pits of inferno/poi_vocations_door.lua b/data/actions/scripts/custom/quests/pits of inferno/poi_vocations_door.lua similarity index 100% rename from data/actions/scripts/quests/pits of inferno/poi_vocations_door.lua rename to data/actions/scripts/custom/quests/pits of inferno/poi_vocations_door.lua diff --git a/data/actions/scripts/custom/quests/quests.lua b/data/actions/scripts/custom/quests/quests.lua new file mode 100644 index 0000000..258c4ac --- /dev/null +++ b/data/actions/scripts/custom/quests/quests.lua @@ -0,0 +1,27 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + if item.uid <= 1250 or item.uid >= 20000 then + return false + end + + local itemType = ItemType(item.uid) + if itemType:getId() == 0 then + return false + end + + if player:getStorageValue(item.uid) == -1 then + local itemWeight = itemType:getWeight() + local playerCap = player:getFreeCapacity() + if playerCap >= itemWeight then + player:addItem(item.uid, 1) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, 'You have found a ' .. itemType:getName() .. '.') + player:setStorageValue(item.uid, 1) + else + player:sendCancelMessage("You don't have capacity.") + player:getPosition():sendMagicEffect(CONST_ME_POFF) + end + else + player:sendCancelMessage("It is empty.") + player:getPosition():sendMagicEffect(CONST_ME_POFF) + end + return true +end diff --git a/data/actions/scripts/other/bed_modification_kits.lua b/data/actions/scripts/other/bed_modification_kits.lua index 5ef34e6..f94356f 100644 --- a/data/actions/scripts/other/bed_modification_kits.lua +++ b/data/actions/scripts/other/bed_modification_kits.lua @@ -33,12 +33,12 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) end for _, bed in pairs(beds) do - if bed[1][1] == targetItemId or table.contains({1758, 5502, 18027}, targetItemId) then + if bed[1][1] == targetItemId or table.contains({1758, 5502}, targetItemId) then toPosition:sendMagicEffect(CONST_ME_POFF) toPosition.y = toPosition.y + 1 internalBedTransform(item, target, toPosition, newBed[1]) break - elseif bed[2][1] == targetItemId or table.contains({1756, 5500, 18029}, targetItemId) then + elseif bed[2][1] == targetItemId or table.contains({1756, 5500}, targetItemId) then toPosition:sendMagicEffect(CONST_ME_POFF) toPosition.x = toPosition.x + 1 internalBedTransform(item, target, toPosition, newBed[2]) diff --git a/data/actions/scripts/other/bird_cage.lua b/data/actions/scripts/other/bird_cage.lua new file mode 100644 index 0000000..bec3614 --- /dev/null +++ b/data/actions/scripts/other/bird_cage.lua @@ -0,0 +1,9 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + if math.random(100) == 1 then + item:transform(2094) + player:addAchievement("Oops") + else + item:getPosition():sendMagicEffect(CONST_ME_SOUND_YELLOW) + end + return true +end diff --git a/data/actions/scripts/other/blueberry_bush.lua b/data/actions/scripts/other/blueberry_bush.lua new file mode 100644 index 0000000..d158f67 --- /dev/null +++ b/data/actions/scripts/other/blueberry_bush.lua @@ -0,0 +1,7 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + item:transform(2786) + item:decay() + Game.createItem(2677, 3, fromPosition) + player:addAchievementProgress("Bluebarian", 500) + return true +end diff --git a/data/actions/scripts/other/changegold.lua b/data/actions/scripts/other/change_gold.lua similarity index 100% rename from data/actions/scripts/other/changegold.lua rename to data/actions/scripts/other/change_gold.lua diff --git a/data/actions/scripts/other/constructionkits.lua b/data/actions/scripts/other/construction_kits.lua similarity index 74% rename from data/actions/scripts/other/constructionkits.lua rename to data/actions/scripts/other/construction_kits.lua index 23eeb88..bcb1215 100644 --- a/data/actions/scripts/other/constructionkits.lua +++ b/data/actions/scripts/other/construction_kits.lua @@ -9,7 +9,7 @@ local constructionKits = { [3936] = 3811, [3937] = 2101, [3938] = 3812, [5086] = 5046, [5087] = 5055, [5088] = 5056, [6114] = 6111, [6115] = 6109, [6372] = 6356, [6373] = 6371, [8692] = 8688, [9974] = 9975, [11126] = 11127, [11133] = 11129, [11124] = 11125, - [11205] = 11203, + [11205] = 11203 } function onUse(player, item, fromPosition, target, toPosition, isHotkey) @@ -18,13 +18,16 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) return false end - if fromPosition.x == CONTAINER_POSITION then - player:sendTextMessage(MESSAGE_STATUS_SMALL, "Put the construction kit on the floor first.") - elseif not Tile(fromPosition):getHouse() then - player:sendTextMessage(MESSAGE_STATUS_SMALL, "You may construct this only inside a house.") + local tile = Tile(item:getPosition()) + if tile and tile:getHouse() then + if fromPosition.x ~= CONTAINER_POSITION or item:getParent():getId() == ITEM_BROWSEFIELD then + item:transform(kit) + fromPosition:sendMagicEffect(CONST_ME_POFF) + else + player:sendTextMessage(MESSAGE_STATUS_SMALL, "Put the construction kit on the floor first.") + end else - item:transform(kit) - fromPosition:sendMagicEffect(CONST_ME_POFF) + player:sendTextMessage(MESSAGE_STATUS_SMALL, "You may construct this only inside a house.") end return true end diff --git a/data/actions/scripts/other/createbread.lua b/data/actions/scripts/other/create_bread.lua similarity index 100% rename from data/actions/scripts/other/createbread.lua rename to data/actions/scripts/other/create_bread.lua diff --git a/data/actions/scripts/other/decayto.lua b/data/actions/scripts/other/decayto.lua index d3062d3..6c1113e 100644 --- a/data/actions/scripts/other/decayto.lua +++ b/data/actions/scripts/other/decayto.lua @@ -1,26 +1,26 @@ --- keep in sync with actions.xml local decayItems = { - [1479] = 1480, [1480] = 1479, [1634] = 1635, [1635] = 1634, [1636] = 1637, - [1637] = 1636, [1638] = 1639, [1639] = 1638, [1640] = 1641, [1641] = 1640, - [1786] = 1787, [1787] = 1786, [1788] = 1789, [1789] = 1788, [1790] = 1791, - [1791] = 1790, [1792] = 1793, [1793] = 1792, [1873] = 1874, [1874] = 1873, - [1875] = 1876, [1876] = 1875, [1945] = 1946, [1946] = 1945, [2037] = 2038, - [2038] = 2037, [2039] = 2040, [2040] = 2039, [2041] = 2042, [2042] = 2041, - [2044] = 2045, [2045] = 2044, [2047] = 2048, [2048] = 2047, [2050] = 2051, - [2051] = 2050, [2052] = 2053, [2053] = 2052, [2054] = 2055, [2055] = 2054, - [2058] = 2059, [2059] = 2058, [2060] = 2061, [2061] = 2060, [2064] = 2065, - [2065] = 2064, [2066] = 2067, [2067] = 2066, [2068] = 2069, [2069] = 2068, - [2096] = 2097, [2097] = 2096, [2162] = 2163, [2163] = 2162, [2578] = 2579, - [3947] = 3948, [3948] = 3947, [5812] = 5813, [5813] = 5812, [6489] = 6490, - [6490] = 6489, [7058] = 7059, [7059] = 7058, [8684] = 8685, [8685] = 8684, - [8686] = 8687, [8687] = 8686, [8688] = 8689, [8689] = 8688, [8690] = 8691, - [8691] = 8690, [9575] = 9576, [9576] = 9575, [9577] = 9578, [9578] = 9577, - [9579] = 9580, [9580] = 9579, [9581] = 9582, [9582] = 9581, [9747] = 9748, - [9748] = 9747, [9749] = 9750, [9750] = 9749, + [1873] = 1874, [1874] = 1873, -- cuckoo clock + [1875] = 1876, [1876] = 1875, -- cuckoo clock + [2041] = 2042, [2042] = 2041, -- candelabrum + [2044] = 2045, [2045] = 2044, -- lamp + [2047] = 2048, [2048] = 2047, -- candlestick + [2050] = 2051, [2051] = 2050, -- torch + [2052] = 2053, [2053] = 2052, -- torch + [2054] = 2055, [2055] = 2054, -- torch + [2162] = 2163, [2163] = 2162, -- magic light wand + [5812] = 5813, [5813] = 5812, -- skull candle + [7183] = 7184, -- baby seal doll + [10719] = 10720, -- friendship amulet + [11401] = 11402, -- Tibiora's box } function onUse(player, item, fromPosition, target, toPosition, isHotkey) - item:transform(decayItems[item.itemid]) + local transformIds = decayItems[item:getId()] + if not transformIds then + return false + end + + item:transform(transformIds) item:decay() return true end diff --git a/data/actions/scripts/other/die.lua b/data/actions/scripts/other/die.lua index 86505a5..e2f4544 100644 --- a/data/actions/scripts/other/die.lua +++ b/data/actions/scripts/other/die.lua @@ -1,7 +1,17 @@ +local depotTiles = {11062, 11063} +local diceEnabledOnDepot = true + function onUse(player, item, fromPosition, target, toPosition, isHotkey) local position = item:getPosition() local value = math.random(1, 6) local isInGhostMode = player:isInGhostMode() + local tile = Tile(player:getPosition()) + if not tile then + return false + end + if table.contains(depotTiles, tile:getGround():getId()) and not diceEnabledOnDepot then + return false + end position:sendMagicEffect(CONST_ME_CRAPS, isInGhostMode and player) diff --git a/data/actions/scripts/other/doors.lua b/data/actions/scripts/other/doors.lua deleted file mode 100644 index 0bd4eb1..0000000 --- a/data/actions/scripts/other/doors.lua +++ /dev/null @@ -1,91 +0,0 @@ -function onUse(player, item, fromPosition, target, toPosition, isHotkey) - local itemId = item:getId() - if table.contains(questDoors, itemId) then - if item.actionid == 9000 and player:getStorageValue(Storage.inquisition) ~= -1 then -- INQUISITION QUEST PERMISSION - item:transform(itemId + 1) - player:teleportTo(toPosition, true) - return true - elseif item.actionid == 9001 and player:getStorageValue(Storage.blueLegsQuest) ~= -1 then -- BLUE LEGS QUEST PERMISSION - item:transform(itemId + 1) - player:teleportTo(toPosition, true) - return true - elseif item.actionid == 9002 and player:getStorageValue(Storage.pitsOfInferno.permission) ~= -1 then -- POI QUEST PERMISSION - item:transform(itemId + 1) - player:teleportTo(toPosition, true) - return true - elseif item.actionid == 9003 and player:getStorageValue(Storage.pitsOfInferno.completed) ~= -1 then -- POI QUEST COMPLETED - item:transform(itemId + 1) - player:teleportTo(toPosition, true) - return true - elseif item.actionid == 9004 and player:getStorageValue(Storage.annihilator) ~= -1 then -- ANIHI QUEST COMPLETED - item:transform(itemId + 1) - player:teleportTo(toPosition, true) - return true - elseif player:getStorageValue(item.actionid) ~= -1 then - item:transform(itemId + 1) - player:teleportTo(toPosition, true) - else - player:sendTextMessage(MESSAGE_INFO_DESCR, "The door seems to be sealed against unwanted intruders.") - end - return true - elseif table.contains(levelDoors, itemId) then - if item.actionid > 0 then - local level = item.actionid - 1000 - if player:getLevel() >= level then - item:transform(itemId + 1) - player:teleportTo(toPosition, true) - else - player:sendTextMessage(MESSAGE_INFO_DESCR, "You need level ".. level .." to pass this door.") - end - else - item:transform(itemId + 1) - player:teleportTo(toPosition, true) - end - return true - elseif table.contains(keys, itemId) then - if target.actionid > 0 then - if item.actionid == target.actionid and doors[target.itemid] then - target:transform(doors[target.itemid]) - return true - end - player:sendTextMessage(MESSAGE_STATUS_SMALL, "The key does not match.") - return true - end - return false - end - - if table.contains(horizontalOpenDoors, itemId) or table.contains(verticalOpenDoors, itemId) then - local doorCreature = Tile(toPosition):getTopCreature() - if doorCreature then - toPosition.x = toPosition.x + 1 - local query = Tile(toPosition):queryAdd(doorCreature, bit.bor(FLAG_IGNOREBLOCKCREATURE, FLAG_PATHFINDING)) - if query ~= RETURNVALUE_NOERROR then - toPosition.x = toPosition.x - 1 - toPosition.y = toPosition.y + 1 - query = Tile(toPosition):queryAdd(doorCreature, bit.bor(FLAG_IGNOREBLOCKCREATURE, FLAG_PATHFINDING)) - end - - if query ~= RETURNVALUE_NOERROR then - player:sendTextMessage(MESSAGE_STATUS_SMALL, Game.getReturnMessage(query)) - return true - end - - doorCreature:teleportTo(toPosition, true) - end - - if not table.contains(openSpecialDoors, itemId) then - item:transform(itemId - 1) - end - return true - end - - if doors[itemId] then - if item.actionid == 0 then - item:transform(doors[itemId]) - else - player:sendTextMessage(MESSAGE_INFO_DESCR, "It is locked.") - end - return true - end - return false -end diff --git a/data/actions/scripts/other/enchanting.lua b/data/actions/scripts/other/enchanting.lua index 1096965..5650df3 100644 --- a/data/actions/scripts/other/enchanting.lua +++ b/data/actions/scripts/other/enchanting.lua @@ -67,18 +67,16 @@ local items = { [COMBAT_FIREDAMAGE] = {id = 8906}, [COMBAT_ICEDAMAGE] = {id = 8907}, [COMBAT_EARTHDAMAGE] = {id = 8909}, [COMBAT_ENERGYDAMAGE] = {id = 8908} }, + [9949] = { -- dracoyle statue + [COMBAT_EARTHDAMAGE] = {id = 9948} -- dracoyle statue (enchanted) + }, + [9954] = { -- dracoyle statue + [COMBAT_EARTHDAMAGE] = {id = 9953} -- dracoyle statue (enchanted) + }, [10022] = { -- worn firewalker boots [COMBAT_FIREDAMAGE] = {id = 9933, say = {text = "Take the boots off first."}}, slot = {type = CONST_SLOT_FEET, check = true} }, - [24716] = { -- werewolf amulet - [COMBAT_NONE] = { - id = 24717, - effects = {failure = CONST_ME_POFF, success = CONST_ME_THUNDER}, - message = {text = "The amulet cannot be enchanted while worn."} - }, - slot = {type = CONST_SLOT_NECKLACE, check = true} - }, charges = 1000, effect = CONST_ME_MAGIC_RED }, @@ -125,7 +123,7 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) end player:addSoul(-items.valuables.soul) player:addMana(-items.valuables.mana) - player:addManaSpent(items.valuables.mana * configManager.getNumber(configKeys.RATE_MAGIC)) + player:addManaSpent(items.valuables.mana) player:addItem(targetType.id) player:getPosition():sendMagicEffect(items.valuables.effect) item:remove(1) diff --git a/data/actions/scripts/other/fireworksrocket.lua b/data/actions/scripts/other/fireworks_rocket.lua similarity index 60% rename from data/actions/scripts/other/fireworksrocket.lua rename to data/actions/scripts/other/fireworks_rocket.lua index 57ce798..e415d02 100644 --- a/data/actions/scripts/other/fireworksrocket.lua +++ b/data/actions/scripts/other/fireworks_rocket.lua @@ -3,12 +3,12 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) fromPosition:sendMagicEffect(math.random(CONST_ME_FIREWORK_YELLOW, CONST_ME_FIREWORK_BLUE)) else local position = player:getPosition() - position:sendMagicEffect(CONST_ME_HITBYFIRE) - position:sendMagicEffect(CONST_ME_EXPLOSIONAREA) + position:sendMagicEffect(CONST_ME_FIREAREA) player:say("Ouch! Rather place it on the ground next time.", TALKTYPE_MONSTER_SAY) - player:addHealth(-10) + player:addAchievementProgress("Rocket in Pocket", 3) + doTargetCombat(0, player, COMBAT_PHYSICALDAMAGE, -10, -10, CONST_ME_EXPLOSIONAREA) end - + player:addAchievementProgress("Fireworks in the Sky", 250) item:remove() return true end diff --git a/data/actions/scripts/other/fluids.lua b/data/actions/scripts/other/fluids.lua index 7ea9f27..b7ce6f8 100644 --- a/data/actions/scripts/other/fluids.lua +++ b/data/actions/scripts/other/fluids.lua @@ -19,9 +19,12 @@ local fluidMessage = { [13] = "Urgh!", [15] = "Aah...", [19] = "Urgh!", + [27] = "Aah...", [43] = "Aaaah..." } +local distillery = {[5513] = 5469, [5514] = 5470} + function onUse(player, item, fromPosition, target, toPosition, isHotkey) local targetItemType = ItemType(target.itemid) if targetItemType and targetItemType:isFluidContainer() then @@ -30,8 +33,8 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) item:transform(item:getId(), 0) return true elseif target.type ~= 0 and item.type == 0 then - target:transform(target:getId(), 0) item:transform(item:getId(), target.type) + target:transform(target:getId(), 0) return true end end @@ -61,6 +64,13 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) local fluidSource = targetItemType and targetItemType:getFluidSource() or 0 if fluidSource ~= 0 then item:transform(item:getId(), fluidSource) + elseif table.contains(distillery, target.itemid) then + local tmp = distillery[target.itemid] + if tmp then + item:transform(item:getId(), 0) + else + player:sendCancelMessage("You have to process the bunch into the distillery to get rum.") + end elseif item.type == 0 then player:sendTextMessage(MESSAGE_STATUS_SMALL, "It is empty.") else diff --git a/data/actions/scripts/other/food.lua b/data/actions/scripts/other/food.lua index fdef030..23ad94c 100644 --- a/data/actions/scripts/other/food.lua +++ b/data/actions/scripts/other/food.lua @@ -39,7 +39,8 @@ local foods = { [2795] = {36, "Munch."}, -- fire mushroom [2796] = {5, "Munch."}, -- green mushroom [5097] = {4, "Yum."}, -- mango - [6125] = {8, "Gulp."}, -- tortoise egg + [5678] = {8, "Gulp."}, -- tortoise egg + [6125] = {8, "Gulp."}, -- tortoise egg from Nargor [6278] = {10, "Mmmm."}, -- cake [6279] = {15, "Mmmm."}, -- decorated cake [6393] = {12, "Mmmm."}, -- valentine's cake diff --git a/data/actions/scripts/other/large_seashell.lua b/data/actions/scripts/other/large_seashell.lua new file mode 100644 index 0000000..5a70e76 --- /dev/null +++ b/data/actions/scripts/other/large_seashell.lua @@ -0,0 +1,24 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + if player:getStorageValue(PlayerStorageKeys.delayLargeSeaShell) <= os.time() then + local chance = math.random(100) + local msg = "" + if chance <= 16 then + doTargetCombat(0, player, COMBAT_PHYSICALDAMAGE, -200, -200, CONST_ME_NONE) + msg = "Ouch! You squeezed your fingers." + elseif chance > 16 and chance <= 64 then + Game.createItem(math.random(7632, 7633), 1, player:getPosition()) + msg = "You found a beautiful pearl." + player:addAchievementProgress("Shell Seeker", 100) + else + msg = "Nothing is inside." + end + player:say(msg, TALKTYPE_MONSTER_SAY, false, player, item:getPosition()) + item:transform(7553) + item:decay() + player:setStorageValue(PlayerStorageKeys.delayLargeSeaShell, os.time() + 20 * 60 * 60) + item:getPosition():sendMagicEffect(CONST_ME_BUBBLES) + else + player:say("You have already opened a shell today.", TALKTYPE_MONSTER_SAY, false, player, item:getPosition()) + end + return true +end diff --git a/data/actions/scripts/other/music.lua b/data/actions/scripts/other/music.lua index 19ba56f..4ff4218 100644 --- a/data/actions/scripts/other/music.lua +++ b/data/actions/scripts/other/music.lua @@ -6,16 +6,16 @@ local instruments = { [2074] = {effect = CONST_ME_SOUND_GREEN}, -- panpipes [2075] = {effect = CONST_ME_SOUND_GREEN}, -- simple fanfare [2076] = {effect = CONST_ME_SOUND_GREEN}, -- fanfare - [2077] = {effect = CONST_ME_SOUND_GREEN}, -- royal fanfare (actual effect is unknown) + [2077] = {effect = CONST_ME_SOUND_GREEN}, -- royal fanfare [2078] = {effect = CONST_ME_SOUND_GREEN}, -- post horn [2079] = {effect = CONST_ME_SOUND_GREEN}, -- war horn - [2080] = {effect = CONST_ME_SOUND_BLUE}, -- piano (actual effect is unknown) - [2081] = {effect = CONST_ME_SOUND_BLUE}, -- piano - [2082] = {effect = CONST_ME_SOUND_BLUE}, -- piano - [2083] = {effect = CONST_ME_SOUND_BLUE}, -- piano - [2084] = {effect = CONST_ME_SOUND_BLUE}, -- harp (actual effect is unknown) - [2085] = {effect = CONST_ME_SOUND_BLUE}, -- harp - [2332] = {effect = CONST_ME_SOUND_BLUE}, -- Waldo's post horn (actual effect is unknown) + [2080] = {effects = {failure = CONST_ME_SOUND_PURPLE, success = CONST_ME_SOUND_GREEN}, chance = 50}, -- piano + [2081] = {effects = {failure = CONST_ME_SOUND_PURPLE, success = CONST_ME_SOUND_GREEN}, chance = 50}, -- piano + [2082] = {effects = {failure = CONST_ME_SOUND_PURPLE, success = CONST_ME_SOUND_GREEN}, chance = 50}, -- piano + [2083] = {effects = {failure = CONST_ME_SOUND_PURPLE, success = CONST_ME_SOUND_GREEN}, chance = 50}, -- piano + [2084] = {effect = CONST_ME_SOUND_GREEN}, -- harp + [2085] = {effect = CONST_ME_SOUND_GREEN}, -- harp + [2332] = {effect = CONST_ME_SOUND_GREEN}, -- Waldo's post horn [2367] = {effect = CONST_ME_SOUND_GREEN}, -- drum (immovable) [2368] = {effect = CONST_ME_SOUND_GREEN}, -- simple fanfare (immovable) [2369] = {effect = CONST_ME_SOUND_YELLOW, itemId = 2681, itemCount = 10, chance = 80, remove = true}, -- cornucopia (immovable) @@ -58,5 +58,6 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) if not chance and instrument.remove then item:remove() end + player:addAchievementProgress("Rockstar", 10000) return true end diff --git a/data/actions/scripts/other/partyhat.lua b/data/actions/scripts/other/party_hat.lua similarity index 85% rename from data/actions/scripts/other/partyhat.lua rename to data/actions/scripts/other/party_hat.lua index 6bc5ccd..55dab6a 100644 --- a/data/actions/scripts/other/partyhat.lua +++ b/data/actions/scripts/other/party_hat.lua @@ -3,7 +3,7 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) if not headSlotItem or item.uid ~= headSlotItem:getUniqueId() then return false end - + player:addAchievementProgress("Party Animal", 200) player:getPosition():sendMagicEffect(CONST_ME_GIFT_WRAPS) return true end diff --git a/data/actions/scripts/other/piggybank.lua b/data/actions/scripts/other/piggy_bank.lua similarity index 85% rename from data/actions/scripts/other/piggybank.lua rename to data/actions/scripts/other/piggy_bank.lua index 07f5d93..fbf2a05 100644 --- a/data/actions/scripts/other/piggybank.lua +++ b/data/actions/scripts/other/piggy_bank.lua @@ -3,6 +3,7 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) item:getPosition():sendMagicEffect(CONST_ME_POFF) player:addItem(ITEM_GOLD_COIN, 1) item:transform(2115) + player:addAchievementProgress("Allowance Collector", 50) else item:getPosition():sendMagicEffect(CONST_ME_SOUND_YELLOW) player:addItem(ITEM_PLATINUM_COIN, 1) diff --git a/data/actions/scripts/other/potions.lua b/data/actions/scripts/other/potions.lua index 755028d..fe2b9e7 100644 --- a/data/actions/scripts/other/potions.lua +++ b/data/actions/scripts/other/potions.lua @@ -16,20 +16,90 @@ bullseye:setParameter(CONDITION_PARAM_SKILL_SHIELD, -10) bullseye:setParameter(CONDITION_PARAM_BUFF_SPELL, true) local potions = { - [6558] = {transform = {7588, 7589}, effect = CONST_ME_DRAWBLOOD}, - [7439] = {condition = berserk, vocations = {4, 8}, effect = CONST_ME_MAGIC_RED, description = "Only knights may drink this potion.", text = "You feel stronger."}, - [7440] = {condition = mastermind, vocations = {1, 2, 5, 6}, effect = CONST_ME_MAGIC_BLUE, description = "Only sorcerers and druids may drink this potion.", text = "You feel smarter."}, - [7443] = {condition = bullseye, vocations = {3, 7}, effect = CONST_ME_MAGIC_GREEN, description = "Only paladins may drink this potion.", text = "You feel more accurate."}, - [7588] = {health = {250, 350}, vocations = {3, 4, 7, 8}, level = 50, flask = 7634, description = "Only knights and paladins of level 50 or above may drink this fluid."}, - [7589] = {mana = {115, 185}, vocations = {1, 2, 3, 5, 6, 7}, level = 50, flask = 7634, description = "Only sorcerers, druids and paladins of level 50 or above may drink this fluid."}, - [7590] = {mana = {150, 250}, vocations = {1, 2, 5, 6}, level = 80, flask = 7635, description = "Only druids and sorcerers of level 80 or above may drink this fluid."}, - [7591] = {health = {425, 575}, vocations = {4, 8}, level = 80, flask = 7635, description = "Only knights of level 80 or above may drink this fluid."}, - [7618] = {health = {125, 175}, flask = 7636}, - [7620] = {mana = {75, 125}, flask = 7636}, - [8472] = {health = {250, 350}, mana = {100, 200}, vocations = {3, 7}, level = 80, flask = 7635, description = "Only paladins of level 80 or above may drink this fluid."}, - [8473] = {health = {650, 850}, vocations = {4, 8}, level = 130, flask = 7635, description = "Only knights of level 130 or above may drink this fluid."}, - [8474] = {antidote = true, flask = 7636}, - [8704] = {health = {60, 90}, flask = 7636}, + [6558] = { -- concentrated demonic blood + transform = {7588, 7589}, + effect = CONST_ME_DRAWBLOOD + }, + [7439] = { -- berserk potion + condition = berserk, + vocations = {4, 8}, + effect = CONST_ME_MAGIC_RED, + description = "Only knights may drink this potion.", + text = "You feel stronger." + }, + [7440] = { -- mastermind potion + condition = mastermind, + vocations = {1, 2, 5, 6}, + effect = CONST_ME_MAGIC_BLUE, + description = "Only sorcerers and druids may drink this potion.", + text = "You feel smarter." + }, + [7443] = { -- bullseye potion + condition = bullseye, + vocations = {3, 7}, + effect = CONST_ME_MAGIC_GREEN, + description = "Only paladins may drink this potion.", + text = "You feel more accurate." + }, + [7588] = { -- strong health potion + health = {250, 350}, + vocations = {3, 4, 7, 8}, + level = 50, + flask = 7634, + description = "Only knights and paladins of level 50 or above may drink this fluid." + }, + [7589] = { -- strong mana potion + mana = {115, 185}, + vocations = {1, 2, 3, 5, 6, 7}, + level = 50, + flask = 7634, + description = "Only sorcerers, druids and paladins of level 50 or above may drink this fluid." + }, + [7590] = { -- great mana potion + mana = {150, 250}, + vocations = {1, 2, 5, 6}, + level = 80, + flask = 7635, + description = "Only druids and sorcerers of level 80 or above may drink this fluid." + }, + [7591] = { -- great health potion + health = {425, 575}, + vocations = {4, 8}, + level = 80, + flask = 7635, + description = "Only knights of level 80 or above may drink this fluid." + }, + [7618] = { -- health potion + health = {125, 175}, + flask = 7636 + }, + [7620] = { -- mana potion + mana = {75, 125}, + flask = 7636 + }, + [8472] = { -- great spirit potion + health = {250, 350}, + mana = {100, 200}, + vocations = {3, 7}, + level = 80, + flask = 7635, + description = "Only paladins of level 80 or above may drink this fluid." + }, + [8473] = { -- ultimate health potion + health = {650, 850}, + vocations = {4, 8}, + level = 130, + flask = 7635, + description = "Only knights of level 130 or above may drink this fluid." + }, + [8474] = { -- antidote potion + antidote = true, + flask = 7636, + }, + [8704] = { -- small health potion + health = {60, 90}, + flask = 7636, + } } function onUse(player, item, fromPosition, target, toPosition, isHotkey) @@ -38,7 +108,7 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) end local potion = potions[item:getId()] - if potion.level and player:getLevel() < potion.level or potion.vocations and not table.contains(potion.vocations, player:getVocation():getBase():getId()) and not (player:getGroup():getId() >= 2) then + if potion.level and player:getLevel() < potion.level or potion.vocations and not table.contains(potion.vocations, player:getVocation():getId()) then player:say(potion.description, TALKTYPE_MONSTER_SAY) return true end @@ -48,23 +118,31 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) player:say(potion.text, TALKTYPE_MONSTER_SAY) player:getPosition():sendMagicEffect(potion.effect) elseif potion.transform then - item:transform(potion.transform[math.random(#potion.transform)]) + local reward = potion.transform[math.random(#potion.transform)] + if fromPosition.x == CONTAINER_POSITION then + local targetContainer = Container(item:getParent().uid) + targetContainer:addItem(reward, 1) + else + Game.createItem(reward, 1, fromPosition) + end item:getPosition():sendMagicEffect(potion.effect) + item:remove(1) return true else if potion.health then - doTargetCombatHealth(0, target, COMBAT_HEALING, potion.health[1], potion.health[2]) + doTargetCombat(0, target, COMBAT_HEALING, potion.health[1], potion.health[2]) end if potion.mana then - doTargetCombatMana(0, target, potion.mana[1], potion.mana[2]) + doTargetCombat(0, target, COMBAT_MANADRAIN, potion.mana[1], potion.mana[2]) end if potion.antidote then target:removeCondition(CONDITION_POISON) end - -- player:addItem(potion.flask) + player:addAchievementProgress("Potion Addict", 100000) + player:addItem(potion.flask) target:say("Aaaah...", TALKTYPE_MONSTER_SAY) target:getPosition():sendMagicEffect(CONST_ME_MAGIC_BLUE) end diff --git a/data/actions/scripts/other/snow_heap.lua b/data/actions/scripts/other/snow_heap.lua new file mode 100644 index 0000000..97739fb --- /dev/null +++ b/data/actions/scripts/other/snow_heap.lua @@ -0,0 +1,4 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + Game.createItem(2111, 1, item:getPosition()) + return true +end diff --git a/data/actions/scripts/other/spellbook.lua b/data/actions/scripts/other/spellbook.lua deleted file mode 100644 index 14f3fa3..0000000 --- a/data/actions/scripts/other/spellbook.lua +++ /dev/null @@ -1,30 +0,0 @@ -function onUse(player, item, fromPosition, target, toPosition, isHotkey) - local text = "" - local spells = {} - for _, spell in ipairs(player:getInstantSpells()) do - if spell.level ~= 0 then - if spell.manapercent > 0 then - spell.mana = spell.manapercent .. "%" - end - spells[#spells + 1] = spell - end - end - - table.sort(spells, function(a, b) return a.level < b.level end) - - local prevLevel = -1 - for i, spell in ipairs(spells) do - local line = "" - if prevLevel ~= spell.level then - if i ~= 1 then - line = "\n" - end - line = line .. "Spells for Level " .. spell.level .. "\n" - prevLevel = spell.level - end - text = text .. line .. " " .. spell.words .. " - " .. spell.name .. " : " .. spell.mana .. "\n" - end - - player:showTextDialog(item:getId(), text) - return true -end diff --git a/data/actions/scripts/other/surprisebag.lua b/data/actions/scripts/other/surprisebag.lua deleted file mode 100644 index 4727e05..0000000 --- a/data/actions/scripts/other/surprisebag.lua +++ /dev/null @@ -1,27 +0,0 @@ -local presents = { - [6570] = { -- blue present - {2687, 10}, {6394, 3}, 6280, 6574, 6578, 6575, 6577, 6569, 6576, 6572, 2114 - }, - [6571] = { -- red present - {2152, 10}, {2152, 10}, {2152, 10}, 2153, 5944, 2112, 6568, 6566, 2492, 2520, 2195, 2114, 2114, 2114, 6394, 6394, 6576, 6576, 6578, 6578, 6574, 6574 - } -} - -function onUse(player, item, fromPosition, target, toPosition, isHotkey) - local count = 1 - local targetItem = presents[item.itemid] - if not targetItem then - return true - end - - local gift = targetItem[math.random(#targetItem)] - if type(gift) == "table" then - gift = gift[1] - count = gift[2] - end - - player:addItem(gift, count) - item:remove(1) - fromPosition:sendMagicEffect(CONST_ME_GIFT_WRAPS) - return true -end diff --git a/data/actions/scripts/other/teleport.lua b/data/actions/scripts/other/teleport.lua index 7553f58..f03128c 100644 --- a/data/actions/scripts/other/teleport.lua +++ b/data/actions/scripts/other/teleport.lua @@ -1,11 +1,5 @@ -local upFloorIds = {1386, 3678, 5543, 8599, 10035} -local draw_well = 1369 - +local upFloorIds = {1386, 3678, 5543} function onUse(player, item, fromPosition, target, toPosition, isHotkey) - if item.itemid == draw_well and item.actionid ~= 100 then - return false - end - if table.contains(upFloorIds, item.itemid) then fromPosition:moveUpstairs() else diff --git a/data/actions/scripts/other/transforms.lua b/data/actions/scripts/other/transforms.lua deleted file mode 100644 index f8b2346..0000000 --- a/data/actions/scripts/other/transforms.lua +++ /dev/null @@ -1,13 +0,0 @@ -local transformItems = { - [3743] = 4404, [4404] = 3743 -} - -function onUse(player, item, fromPosition, target, toPosition, isHotkey) - local transformIds = transformItems[item:getId()] - if not transformIds then - return false - end - - item:transform(transformIds) - return true -end diff --git a/data/actions/scripts/other/wall_mirror.lua b/data/actions/scripts/other/wall_mirror.lua new file mode 100644 index 0000000..3e965b2 --- /dev/null +++ b/data/actions/scripts/other/wall_mirror.lua @@ -0,0 +1,24 @@ +local messages = { + "You could win a beauty contest today!", + "You rarely looked better.", + "Well, you can't look good every day.", + "You should think about a makeover.", + "Is that the indication of a potbelly looming under your clothes?", + "You look irresistible.", + "You look tired.", + "You look awesome!", + "You nearly don't recognize yourself.", + "You look fabulous.", + "Surprise, surprise, you don't see yourself." +} + +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + if player:getStorageValue(PlayerStorageKeys.delayWallMirror) <= os.time() then + player:say(messages[math.random(1, #messages)], TALKTYPE_MONSTER_SAY) + player:setStorageValue(PlayerStorageKeys.delayWallMirror, os.time() + 20 * 60 * 60) + player:addAchievementProgress("Vanity", 300) + else + player:say("Don't be so vain about your appearance.", TALKTYPE_MONSTER_SAY) + end + return true +end diff --git a/data/actions/scripts/other/water_pipe.lua b/data/actions/scripts/other/water_pipe.lua new file mode 100644 index 0000000..41a37ed --- /dev/null +++ b/data/actions/scripts/other/water_pipe.lua @@ -0,0 +1,8 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + if math.random(3) == 1 then + item:getPosition():sendMagicEffect(CONST_ME_POFF) + else + player:getPosition():sendMagicEffect(CONST_ME_POFF) + end + return true +end diff --git a/data/actions/scripts/other/windows.lua b/data/actions/scripts/other/windows.lua deleted file mode 100644 index f88bccb..0000000 --- a/data/actions/scripts/other/windows.lua +++ /dev/null @@ -1,45 +0,0 @@ -local windows = { - [5303] = 6448, [5304] = 6449, [6438] = 6436, [6436] = 6438, - [6439] = 6437, [6437] = 6439, [6442] = 6440, [6440] = 6442, - [6443] = 6441, [6441] = 6443, [6446] = 6444, [6444] = 6446, - [6447] = 6445, [6445] = 6447, [6448] = 5303, [6449] = 5304, - [6452] = 6450, [6450] = 6452, [6453] = 6451, [6451] = 6453, - [6456] = 6454, [6454] = 6456, [6457] = 6455, [6455] = 6457, - [6460] = 6458, [6458] = 6460, [6461] = 6459, [6459] = 6461, - [6464] = 6462, [6462] = 6464, [6465] = 6463, [6463] = 6465, - [6468] = 6466, [6466] = 6468, [6469] = 6467, [6467] = 6469, - [6472] = 6470, [6470] = 6472, [6473] = 6471, [6471] = 6473, - [6790] = 6788, [6788] = 6790, [6791] = 6789, [6789] = 6791, - [7027] = 7025, [7025] = 7027, [7028] = 7026, [7026] = 7028, - [7031] = 7029, [7029] = 7031, [7032] = 7030, [7030] = 7032, - [10264] = 10266, [10266] = 10264, [10265] = 10267, [10267] = 10265, - [10488] = 10490, [10490] = 10488, [10489] = 10491, [10491] = 10489, -} - -function onUse(player, item, fromPosition, target, toPosition, isHotkey) - local window = windows[item:getId()] - if not window then - return false - end - - local tile = Tile(fromPosition) - local house = tile and tile:getHouse() - if not house then - fromPosition.y = fromPosition.y - 1 - tile = Tile(fromPosition) - house = tile and tile:getHouse() - if not house then - fromPosition.y = fromPosition.y + 1 - fromPosition.x = fromPosition.x - 1 - tile = Tile(fromPosition) - house = tile and tile:getHouse() - end - end - - if house and player:getTile():getHouse() ~= house and player:getAccountType() < ACCOUNT_TYPE_GAMEMASTER then - return false - end - - item:transform(window) - return true -end diff --git a/data/actions/scripts/quests/annihilator.lua b/data/actions/scripts/quests/annihilator.lua new file mode 100644 index 0000000..77cba96 --- /dev/null +++ b/data/actions/scripts/quests/annihilator.lua @@ -0,0 +1,36 @@ +local playerPosition = { + {x = 247, y = 659, z = 13}, + {x = 247, y = 660, z = 13}, + {x = 247, y = 661, z = 13}, + {x = 247, y = 662, z = 13} +} +local newPosition = { + {x = 189, y = 650, z = 13}, + {x = 189, y = 651, z = 13}, + {x = 189, y = 652, z = 13}, + {x = 189, y = 653, z = 13} +} + +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + if item.itemid == 1945 then + local players = {} + for _, position in ipairs(playerPosition) do + local topPlayer = Tile(position):getTopCreature() + if not topPlayer or not topPlayer:isPlayer() or topPlayer:getLevel() < 100 or topPlayer:getStorageValue(PlayerStorageKeys.annihilatorReward) ~= -1 then + player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return false + end + players[#players + 1] = topPlayer + end + + for i, targetPlayer in ipairs(players) do + Position(playerPosition[i]):sendMagicEffect(CONST_ME_POFF) + targetPlayer:teleportTo(newPosition[i], false) + targetPlayer:getPosition():sendMagicEffect(CONST_ME_ENERGYAREA) + end + item:transform(1946) + elseif item.itemid == 1946 then + player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + end + return true +end diff --git a/data/actions/scripts/quests/quests.lua b/data/actions/scripts/quests/quests.lua index 258c4ac..94348ca 100644 --- a/data/actions/scripts/quests/quests.lua +++ b/data/actions/scripts/quests/quests.lua @@ -1,5 +1,6 @@ +local annihilatorReward = {1990, 2400, 2431, 2494} function onUse(player, item, fromPosition, target, toPosition, isHotkey) - if item.uid <= 1250 or item.uid >= 20000 then + if item.uid <= 1250 or item.uid >= 30000 then return false end @@ -8,20 +9,35 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) return false end - if player:getStorageValue(item.uid) == -1 then - local itemWeight = itemType:getWeight() - local playerCap = player:getFreeCapacity() + local itemWeight = itemType:getWeight() + local playerCap = player:getFreeCapacity() + if table.contains(annihilatorReward, item.uid) then + if player:getStorageValue(PlayerStorageKeys.annihilatorReward) == -1 then + if playerCap >= itemWeight then + if item.uid == 1990 then + player:addItem(1990, 1):addItem(2326, 1) + else + player:addItem(item.uid, 1) + end + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, 'You have found a ' .. itemType:getName() .. '.') + player:setStorageValue(PlayerStorageKeys.annihilatorReward, 1) + player:addAchievement("Annihilator") + else + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, 'You have found a ' .. itemType:getName() .. ' weighing ' .. itemWeight .. ' oz it\'s too heavy.') + end + else + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "It is empty.") + end + elseif player:getStorageValue(item.uid) == -1 then if playerCap >= itemWeight then - player:addItem(item.uid, 1) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, 'You have found a ' .. itemType:getName() .. '.') + player:addItem(item.uid, 1) player:setStorageValue(item.uid, 1) else - player:sendCancelMessage("You don't have capacity.") - player:getPosition():sendMagicEffect(CONST_ME_POFF) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, 'You have found a ' .. itemType:getName() .. ' weighing ' .. itemWeight .. ' oz it\'s too heavy.') end else - player:sendCancelMessage("It is empty.") - player:getPosition():sendMagicEffect(CONST_ME_POFF) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "It is empty.") end return true end diff --git a/data/actions/scripts/tools/crowbar.lua b/data/actions/scripts/tools/crowbar.lua new file mode 100644 index 0000000..31b8aba --- /dev/null +++ b/data/actions/scripts/tools/crowbar.lua @@ -0,0 +1,3 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + return onUseCrowbar(player, item, fromPosition, target, toPosition, isHotkey) +end diff --git a/data/actions/scripts/tools/fishing.lua b/data/actions/scripts/tools/fishing.lua index cd58271..7018de0 100644 --- a/data/actions/scripts/tools/fishing.lua +++ b/data/actions/scripts/tools/fishing.lua @@ -1,4 +1,4 @@ -local waterIds = {493, 4608, 4609, 4610, 4611, 4612, 4613, 4614, 4615, 4616, 4617, 4618, 4619, 4620, 4621, 4622, 4623, 4624, 4625, 7236, 10499, 15401, 15402} +local waterIds = {493, 4608, 4609, 4610, 4611, 4612, 4613, 4614, 4615, 4616, 4617, 4618, 4619, 4620, 4621, 4622, 4623, 4624, 4625, 7236, 10499} local lootTrash = {2234, 2238, 2376, 2509, 2667} local lootCommon = {2152, 2167, 2168, 2669, 7588, 7589} local lootRare = {2143, 2146, 2149, 7158, 7159} @@ -7,7 +7,7 @@ local useWorms = true function onUse(player, item, fromPosition, target, toPosition, isHotkey) local targetId = target.itemid - if not table.contains(waterIds, target.itemid) then + if not table.contains(waterIds, targetId) then return false end @@ -19,7 +19,8 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) end toPosition:sendMagicEffect(CONST_ME_WATERSPLASH) - target:remove() + target:transform(targetId + 1) + target:decay() local rareChance = math.random(1, 100) if rareChance == 1 then @@ -38,7 +39,7 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) toPosition:sendMagicEffect(CONST_ME_LOSEENERGY) end - if targetId == 493 or targetId == 15402 then + if targetId == 493 then return true end @@ -48,17 +49,10 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) return true end - if targetId == 15401 then - target:transform(targetId + 1) - target:decay() - - if math.random(1, 100) >= 97 then - player:addItem(15405, 1) - return true - end - elseif targetId == 7236 then + if targetId == 7236 then target:transform(targetId + 1) target:decay() + player:addAchievementProgress("Exquisite Taste", 250) local rareChance = math.random(1, 100) if rareChance == 1 then @@ -72,7 +66,8 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) return true end end - player:addItem(2267, 1) + player:addAchievementProgress("Here, Fishy Fishy!", 1000) + player:addItem(2667, 1) end return true end diff --git a/data/actions/scripts/tools/kitchen_knife.lua b/data/actions/scripts/tools/kitchen_knife.lua new file mode 100644 index 0000000..3e276e0 --- /dev/null +++ b/data/actions/scripts/tools/kitchen_knife.lua @@ -0,0 +1,3 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + return onUseKitchenKnife(player, item, fromPosition, target, toPosition, isHotkey) +end diff --git a/data/actions/scripts/tools/scythe.lua b/data/actions/scripts/tools/scythe.lua index c98d85e..7ca273d 100644 --- a/data/actions/scripts/tools/scythe.lua +++ b/data/actions/scripts/tools/scythe.lua @@ -1,3 +1,3 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) return onUseScythe(player, item, fromPosition, target, toPosition, isHotkey) -end \ No newline at end of file +end diff --git a/data/actions/scripts/tools/tool_gear.lua b/data/actions/scripts/tools/tool_gear.lua new file mode 100644 index 0000000..3ee3636 --- /dev/null +++ b/data/actions/scripts/tools/tool_gear.lua @@ -0,0 +1,17 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + if math.random(100) > 5 then + return onUseRope(player, item, fromPosition, target, toPosition, isHotkey) + or onUseShovel(player, item, fromPosition, target, toPosition, isHotkey) + or onUsePick(player, item, fromPosition, target, toPosition, isHotkey) + or onUseMachete(player, item, fromPosition, target, toPosition, isHotkey) + or onUseCrowbar(player, item, fromPosition, target, toPosition, isHotkey) + or onUseScythe(player, item, fromPosition, target, toPosition, isHotkey) + or onUseKitchenKnife(player, item, fromPosition, target, toPosition, isHotkey) + else + player:say("Oh no! Your tool is jammed and can't be used for a minute.", TALKTYPE_MONSTER_SAY) + player:addAchievementProgress("Bad Timming", 10) + item:transform(item.itemid + 1) + item:decay() + end + return true +end diff --git a/data/chatchannels/chatchannels.xml b/data/chatchannels/chatchannels.xml index dfbf120..dc79224 100644 --- a/data/chatchannels/chatchannels.xml +++ b/data/chatchannels/chatchannels.xml @@ -7,7 +7,4 @@ - - - diff --git a/data/chatchannels/scripts/guildleaders.lua b/data/chatchannels/scripts/guildleaders.lua deleted file mode 100644 index bd0dcdc..0000000 --- a/data/chatchannels/scripts/guildleaders.lua +++ /dev/null @@ -1,20 +0,0 @@ -function canJoin(player) - return player:getGuildLevel() == 3 or player:getGroup():getAccess() -end - -function onSpeak(player, type, message) - local staff = player:getGroup():getAccess() - local guild = player:getGuild() - local info = "staff" - type = TALKTYPE_CHANNEL_Y - if staff then - if guild then - info = info .. "][" .. guild:getName() - end - type = TALKTYPE_CHANNEL_O - else - info = guild:getName() - end - sendChannelMessage(10, type, player:getName() .. " [" .. info .. "]: " .. message) - return false -end diff --git a/data/creaturescripts/creaturescripts.xml b/data/creaturescripts/creaturescripts.xml index fb2dc42..a3124df 100644 --- a/data/creaturescripts/creaturescripts.xml +++ b/data/creaturescripts/creaturescripts.xml @@ -1,37 +1,40 @@ - + - - - - - - - - - - - - - + + + + + - - - - + + + + + + + + + + + + + + + - - - + + + - + - + - + diff --git a/data/creaturescripts/scripts/boss.lua b/data/creaturescripts/scripts/boss.lua deleted file mode 100644 index bb086e1..0000000 --- a/data/creaturescripts/scripts/boss.lua +++ /dev/null @@ -1,236 +0,0 @@ -function onCreatureAppear(self, creature) - if self == creature then - if self:getType():isRewardBoss() then - self:setReward(true) - end - end -end - -local function pushSeparated(buffer, sep, ...) - local argv = {...} - local argc = #argv - for k, v in ipairs(argv) do - table.insert(buffer, v) - if k < argc and sep then - table.insert(buffer, sep) - end - end -end - -local function insertItems(buffer, info, parent, items) - local start = info.running - for _, item in ipairs(items) do - if _ ~= 1 or parent > 100 then - table.insert(buffer, ",") - end - - info.running = info.running + 1 - table.insert(buffer, "(") - pushSeparated(buffer, ",", info.playerGuid, parent, info.running, item:getId(), item:getSubType(), db.escapeBlob(item:serializeAttributes())) - table.insert(buffer, ")") - - if item:isContainer() then - local size = item:getSize() - if size > 0 then - local subItems = {} - for i = 1, size do - table.insert(subItems, item:getItem(i - 1)) - end - - insertItems(buffer, info, info.running, subItems) - end - end - end - return info.running - start -end - -local function insertRewardItems(playerGuid, timestamp, itemList) - db.asyncStoreQuery('SELECT `pid`, `sid` FROM `player_rewards` WHERE player_id = ' .. playerGuid .. ' ORDER BY `sid` ASC;', - function(query) - local lastReward = 0 - local lastStoreId - if (query) then - repeat - local sid = result.getDataInt(query, 'sid') - local pid = result.getDataInt(query, 'pid') - - if pid < 100 then - lastReward = pid - end - lastStoreId = sid - until not result.next(query) - end - - local buffer = {'INSERT INTO `player_rewards` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES'} - - --reward bag - local info = { - playerGuid = playerGuid, - running = lastStoreId or 100 - } - - local bag = Game.createItem(ITEM_REWARD_CONTAINER) - bag:setAttribute(ITEM_ATTRIBUTE_DATE, timestamp) - if itemList then - for _, p in ipairs(itemList) do - bag:addItem(p[1], p[2]) - end - end - - local total = insertItems(buffer, info, lastReward + 1, {bag}) - table.insert(buffer, ";") - - if total ~= 0 then - db.query(table.concat(buffer)) - end - end - ) -end - -local function getPlayerStats(bossId, playerGuid, autocreate) - local ret = globalBosses[bossId][playerGuid] - if not ret and autocreate then - ret = { - bossId = bossId, - damageIn = 0, -- damage taken from the boss - healing = 0, -- healing (other players) done - } - globalBosses[bossId][playerGuid] = ret - return ret - end - return ret -end - -function onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified) - local monsterType = creature:getType() - if monsterType:isRewardBoss() then -- Make sure it is a boss - local bossId = creature:getId() - local timestamp = os.time() - - local totalDamageOut, totalDamageIn, totalHealing = 0.1, 0.1, 0.1 -- avoid dividing by zero - - local scores = {} - local info = globalBosses[bossId] - local damageMap = creature:getDamageMap() - - for guid, stats in pairs(info) do - local player = Player(stats.playerId) - local part = damageMap[stats.playerId] - local damageOut, damageIn, healing = (stats.damageOut or 0) + (part and part.total or 0), stats.damageIn or 0, stats.healing or 0 - - totalDamageOut = totalDamageOut + damageOut - totalDamageIn = totalDamageIn + damageIn - totalHealing = totalHealing + healing - - table.insert(scores, { - player = player, - guid = guid, - damageOut = damageOut, - damageIn = damageIn, - healing = healing, - }) - end - - local participants = 0 - for _, con in ipairs(scores) do - local score = (con.damageOut / totalDamageOut) + (con.damageIn / totalDamageIn) + (con.healing / totalHealing) - con.score = score / 3 -- normalize to 0-1 - if score ~= 0 then - participants = participants + 1 - end - end - table.sort(scores, function(a, b) return a.score > b.score end) - - local expectedScore = 1 / participants - - for _, con in ipairs(scores) do - local reward, stamina -- ignoring stamina for now because I heard you receive rewards even when it's depleted - if con.player then - reward = con.player:getReward(timestamp, true) - stamina = con.player:getStamina() - else - stamina = con.stamina or 0 - end - - local playerLoot - if --[[stamina > 840 and]] con.score ~= 0 then - local lootFactor = 1.000 - lootFactor = lootFactor / participants ^ (1 / 3) -- tone down the loot a notch if there are many participants - lootFactor = lootFactor * (1 + lootFactor) ^ (con.score / expectedScore) -- increase the loot multiplicatively by how many times the player surpassed the expected score - playerLoot = monsterType:getBossReward(lootFactor, _ == 1) - - if con.player then - for _, p in ipairs(playerLoot) do - reward:addItem(p[1], p[2]) - end - end - end - - if con.player then - local lootMessage = {"The following items are available in your reward chest: "} - - if --[[stamina > 840]]true then - reward:getContentDescription(lootMessage) - else - table.insert(lootMessage, 'nothing (due to low stamina)') - end - table.insert(lootMessage, ".") - con.player:sendTextMessage(MESSAGE_EVENT_ADVANCE, table.concat(lootMessage)) - else - insertRewardItems(con.guid, timestamp, playerLoot) - end - end - - globalBosses[bossId] = nil - end - return true -end - -function onThink(creature, interval) - local bossId = creature:getId() - local info = globalBosses[bossId] - -- Reset all players' status - for _, player in pairs(info) do - player.active = false - end - - -- Set all players in boss' target list as active in the fight - local targets = creature:getTargetList() - for _, target in ipairs(targets) do - if target:isPlayer() then - local stats = getPlayerStats(bossId, target:getGuid(), true) - stats.playerId = target:getId() -- Update player id - stats.active = true - end - end -end - -function onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin) - if not next(globalBosses) then - return primaryDamage, primaryType, secondaryDamage, secondaryType - end - - if not creature or not attacker then - return primaryDamage, primaryType, secondaryDamage, secondaryType - end - - local stats = creature:inBossFight() - if not stats then - return primaryDamage, primaryType, secondaryDamage, secondaryType - end - - local creatureId, attackerId = creature:getId(), attacker:getId() - stats.playerId = creatureId -- Update player id - - -- Account for healing of others active in the boss fight - if primaryType == COMBAT_HEALING and attacker:isPlayer() and attackerId ~= creatureId then - local healerStats = getPlayerStats(stats.bossId, attacker:getGuid(), true) - healerStats.active = true - healerStats.playerId = attackerId -- Update player id - healerStats.healing = healerStats.healing + primaryDamage - elseif stats.bossId == attackerId then - -- Account for damage taken from the boss - stats.damageIn = stats.damageIn + primaryDamage - end - return primaryDamage, primaryType, secondaryDamage, secondaryType -end diff --git a/data/creaturescripts/scripts/custom/EventLogin.lua b/data/creaturescripts/scripts/custom/EventLogin.lua new file mode 100644 index 0000000..4097350 --- /dev/null +++ b/data/creaturescripts/scripts/custom/EventLogin.lua @@ -0,0 +1,8 @@ +function onLogin(player) + if player:getStorageValue(Storage.events) > 0 then + player:setStorageValue(Storage.events, 0) + player:teleportTo(player:getTown():getTemplePosition()) + end + + return true +end diff --git a/data/creaturescripts/scripts/custom/EventLogout.lua b/data/creaturescripts/scripts/custom/EventLogout.lua new file mode 100644 index 0000000..410735d --- /dev/null +++ b/data/creaturescripts/scripts/custom/EventLogout.lua @@ -0,0 +1,10 @@ +function onLogout(player) + + if player:getStorageValue(Storage.events) > 0 then + player:sendCancelMessage("You can not logout in event!") + player:getPosition():sendMagicEffect(CONST_ME_POFF) + return false + end + + return true +end diff --git a/data/creaturescripts/scripts/advance_save.lua b/data/creaturescripts/scripts/custom/advance_save.lua similarity index 100% rename from data/creaturescripts/scripts/advance_save.lua rename to data/creaturescripts/scripts/custom/advance_save.lua diff --git a/data/creaturescripts/scripts/anti_mc.lua b/data/creaturescripts/scripts/custom/anti_mc.lua similarity index 100% rename from data/creaturescripts/scripts/anti_mc.lua rename to data/creaturescripts/scripts/custom/anti_mc.lua diff --git a/data/creaturescripts/scripts/boss_achievements.lua b/data/creaturescripts/scripts/custom/boss_achievements.lua similarity index 100% rename from data/creaturescripts/scripts/boss_achievements.lua rename to data/creaturescripts/scripts/custom/boss_achievements.lua diff --git a/data/creaturescripts/scripts/custom/deathCast.lua b/data/creaturescripts/scripts/custom/deathCast.lua new file mode 100644 index 0000000..2806079 --- /dev/null +++ b/data/creaturescripts/scripts/custom/deathCast.lua @@ -0,0 +1,6 @@ +function onDeath(player, corpse, killer, mostDamageKiller, unjustified, mostDamageUnjustified) + + if killer:isPlayer() then + Game.broadcastMessage("DeathCast: "..player:getName().." ["..player:getLevel().."] just got killed by "..killer:getName().." ["..killer:getLevel().."].", MESSAGE_STATUS_DEFAULT) + end +end diff --git a/data/creaturescripts/scripts/events/battlefield.lua b/data/creaturescripts/scripts/custom/events/battlefield.lua similarity index 100% rename from data/creaturescripts/scripts/events/battlefield.lua rename to data/creaturescripts/scripts/custom/events/battlefield.lua diff --git a/data/creaturescripts/scripts/events/duca.lua b/data/creaturescripts/scripts/custom/events/duca.lua similarity index 100% rename from data/creaturescripts/scripts/events/duca.lua rename to data/creaturescripts/scripts/custom/events/duca.lua diff --git a/data/creaturescripts/scripts/events/zombie.lua b/data/creaturescripts/scripts/custom/events/zombie.lua similarity index 100% rename from data/creaturescripts/scripts/events/zombie.lua rename to data/creaturescripts/scripts/custom/events/zombie.lua diff --git a/data/creaturescripts/scripts/koshei_the_deathless.lua b/data/creaturescripts/scripts/custom/koshei_the_deathless.lua similarity index 100% rename from data/creaturescripts/scripts/koshei_the_deathless.lua rename to data/creaturescripts/scripts/custom/koshei_the_deathless.lua diff --git a/data/creaturescripts/scripts/level_protection.lua b/data/creaturescripts/scripts/custom/level_protection.lua similarity index 100% rename from data/creaturescripts/scripts/level_protection.lua rename to data/creaturescripts/scripts/custom/level_protection.lua diff --git a/data/creaturescripts/scripts/level_reward.lua b/data/creaturescripts/scripts/custom/level_reward.lua similarity index 100% rename from data/creaturescripts/scripts/level_reward.lua rename to data/creaturescripts/scripts/custom/level_reward.lua diff --git a/data/creaturescripts/scripts/custom/login.lua b/data/creaturescripts/scripts/custom/login.lua new file mode 100644 index 0000000..79a66b1 --- /dev/null +++ b/data/creaturescripts/scripts/custom/login.lua @@ -0,0 +1,36 @@ +local events = { + 'PlayerDeath', + 'DropLoot', + 'AdvanceSave', + 'LevelReward', + 'BossParticipation', + 'KosheiKill', + 'PythiusTheRotten', + 'Tasks', + 'BossAchievements' +} +function onLogin(player) + local serverName = configManager.getString(configKeys.SERVER_NAME) + local loginStr = "Welcome to " .. serverName .. "!" + if player:getLastLoginSaved() <= 0 then + loginStr = loginStr .. " Please choose your outfit." + player:sendOutfitWindow() + else + if loginStr ~= "" then + player:sendTextMessage(MESSAGE_STATUS_DEFAULT, loginStr) + end + + loginStr = string.format("Your last visit in %s: %s.", serverName, os.date("%d %b %Y %X", player:getLastLoginSaved())) + end + player:sendTextMessage(MESSAGE_STATUS_DEFAULT, loginStr) + + -- Stamina + nextUseStaminaTime[player.uid] = 0 + + -- Events + for i = 1, #events do + player:registerEvent(events[i]) + end + + return true +end diff --git a/data/creaturescripts/scripts/offline_training.lua b/data/creaturescripts/scripts/custom/offline_training.lua similarity index 100% rename from data/creaturescripts/scripts/offline_training.lua rename to data/creaturescripts/scripts/custom/offline_training.lua diff --git a/data/creaturescripts/scripts/pvp_arena.lua b/data/creaturescripts/scripts/custom/pvp_arena.lua similarity index 100% rename from data/creaturescripts/scripts/pvp_arena.lua rename to data/creaturescripts/scripts/custom/pvp_arena.lua diff --git a/data/creaturescripts/scripts/pythius_the_rotten.lua b/data/creaturescripts/scripts/custom/pythius_the_rotten.lua similarity index 100% rename from data/creaturescripts/scripts/pythius_the_rotten.lua rename to data/creaturescripts/scripts/custom/pythius_the_rotten.lua diff --git a/data/creaturescripts/scripts/task.lua b/data/creaturescripts/scripts/custom/task.lua similarity index 100% rename from data/creaturescripts/scripts/task.lua rename to data/creaturescripts/scripts/custom/task.lua diff --git a/data/creaturescripts/scripts/drop_loot.lua b/data/creaturescripts/scripts/droploot.lua similarity index 62% rename from data/creaturescripts/scripts/drop_loot.lua rename to data/creaturescripts/scripts/droploot.lua index e29b5d3..bfd9680 100644 --- a/data/creaturescripts/scripts/drop_loot.lua +++ b/data/creaturescripts/scripts/droploot.lua @@ -1,10 +1,11 @@ -function onDeath(player, corpse, killer, mostDamage, unjustified, mostDamage_unjustified) +function onDeath(player, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified) if player:hasFlag(PlayerFlag_NotGenerateLoot) or player:getVocation():getId() == VOCATION_NONE then return true end local amulet = player:getSlotItem(CONST_SLOT_NECKLACE) - if amulet and amulet.itemid == ITEM_AMULETOFLOSS and not table.contains({SKULL_RED, SKULL_BLACK}, player:getSkull()) then + local isRedOrBlack = table.contains({SKULL_RED, SKULL_BLACK}, player:getSkull()) + if amulet and amulet.itemid == ITEM_AMULETOFLOSS and not isRedOrBlack then local isPlayer = false if killer then if killer:isPlayer() then @@ -23,9 +24,10 @@ function onDeath(player, corpse, killer, mostDamage, unjustified, mostDamage_unj else for i = CONST_SLOT_HEAD, CONST_SLOT_AMMO do local item = player:getSlotItem(i) + local lossPercent = player:getLossPercent() if item then - if table.contains({SKULL_RED, SKULL_BLACK}, player:getSkull()) or math.random(item:isContainer() and 100 or 1000) <= player:getLossPercent() then - if not item:moveTo(corpse) then + if isRedOrBlack or math.random(item:isContainer() and 100 or 1000) <= lossPercent then + if (isRedOrBlack or lossPercent ~= 0) and not item:moveTo(corpse) then item:remove() end end diff --git a/data/creaturescripts/scripts/extended_opcode.lua b/data/creaturescripts/scripts/extendedopcode.lua similarity index 100% rename from data/creaturescripts/scripts/extended_opcode.lua rename to data/creaturescripts/scripts/extendedopcode.lua diff --git a/data/creaturescripts/scripts/firstitems.lua b/data/creaturescripts/scripts/firstitems.lua new file mode 100644 index 0000000..69a3efa --- /dev/null +++ b/data/creaturescripts/scripts/firstitems.lua @@ -0,0 +1,12 @@ +local firstItems = {2050, 2382} + +function onLogin(player) + if player:getLastLoginSaved() == 0 then + for i = 1, #firstItems do + player:addItem(firstItems[i], 1) + end + player:addItem(player:getSex() == 0 and 2651 or 2650, 1) + player:addItem(ITEM_BAG, 1):addItem(2674, 1) + end + return true +end diff --git a/data/creaturescripts/scripts/login.lua b/data/creaturescripts/scripts/login.lua index b1ab3a3..e3b1b2e 100644 --- a/data/creaturescripts/scripts/login.lua +++ b/data/creaturescripts/scripts/login.lua @@ -1,81 +1,35 @@ -local function onMovementRemoveProtection(cid, oldPosition, time) - local player = Player(cid) - if not player then - return true - end - - local playerPosition = player:getPosition() - if (playerPosition.x ~= oldPosition.x or playerPosition.y ~= oldPosition.y or playerPosition.z ~= oldPosition.z) or player:getTarget() then - player:setStorageValue(Storage.combatProtectionStorage, 0) - return true - end - - addEvent(onMovementRemoveProtection, 1000, cid, oldPosition, time - 1) -end - -local events = { - 'PlayerDeath', - 'DropLoot', - 'AdvanceSave', - 'LevelReward', - 'BossParticipation', - 'KosheiKill', - 'PythiusTheRotten', - 'Tasks', - 'BossAchievements' -} - function onLogin(player) - local loginStr = "Welcome to " .. configManager.getString(configKeys.SERVER_NAME) .. "! " + local serverName = configManager.getString(configKeys.SERVER_NAME) + local loginStr = "Welcome to " .. serverName .. "!" if player:getLastLoginSaved() <= 0 then loginStr = loginStr .. " Please choose your outfit." player:sendOutfitWindow() - player:setBankBalance(0) else if loginStr ~= "" then player:sendTextMessage(MESSAGE_STATUS_DEFAULT, loginStr) end - loginStr = string.format("Your last visit was on %s.", os.date("%a %b %d %X %Y", player:getLastLoginSaved())) + loginStr = string.format("Your last visit in %s: %s.", serverName, os.date("%d %b %Y %X", player:getLastLoginSaved())) end player:sendTextMessage(MESSAGE_STATUS_DEFAULT, loginStr) - local playerId = player:getId() - -- Stamina - nextUseStaminaTime[playerId] = 1 - - -- EXP Stamina - nextUseXpStamina[playerId] = 1 - - -- Rewards notice - local rewards = #player:getRewardList() - if(rewards > 0) then - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("You have %d %s in your reward chest.", rewards, rewards > 1 and "rewards" or "reward")) - end - - -- Update player id - local stats = player:inBossFight() - if stats then - stats.playerId = player:getId() + nextUseStaminaTime[player.uid] = 0 + + -- Promotion + local vocation = player:getVocation() + local promotion = vocation:getPromotion() + if player:isPremium() then + local value = player:getStorageValue(PlayerStorageKeys.promotion) + if value == 1 then + player:setVocation(promotion) + end + elseif not promotion then + player:setVocation(vocation:getDemotion()) end -- Events - for i = 1, #events do - player:registerEvent(events[i]) - end - - if player:getStorageValue(Storage.combatProtectionStorage) <= os.time() then - player:setStorageValue(Storage.combatProtectionStorage, os.time() + 10) - onMovementRemoveProtection(playerId, player:getPosition(), 10) - end - - db.query('INSERT INTO `players_online` (`player_id`) VALUES (' .. playerId .. ')') - - if player:getStorageValue(Storage.events) > 0 then - player:setStorageValue(Storage.events, 0) - player:teleportTo(player:getTown():getTemplePosition()) - end - + player:registerEvent("PlayerDeath") + player:registerEvent("DropLoot") return true end diff --git a/data/creaturescripts/scripts/logout.lua b/data/creaturescripts/scripts/logout.lua index 0c1d769..7c83ff7 100644 --- a/data/creaturescripts/scripts/logout.lua +++ b/data/creaturescripts/scripts/logout.lua @@ -1,28 +1,7 @@ function onLogout(player) local playerId = player:getId() - db.query("DELETE FROM `players_online` WHERE `player_id` = " .. playerId .. ";") if nextUseStaminaTime[playerId] then nextUseStaminaTime[playerId] = nil end - - local stats = player:inBossFight() - if stats then - -- Player logged out (or died) in the middle of a boss fight, store his damageOut and stamina - local boss = Monster(stats.bossId) - if boss then - local dmgOut = boss:getDamageMap()[playerId] - if dmgOut then - stats.damageOut = (stats.damageOut or 0) + dmgOut.total - end - stats.stamina = player:getStamina() - end - end - - if player:getStorageValue(Storage.events) > 0 then - player:sendCancelMessage("You can not logout in event!") - player:getPosition():sendMagicEffect(CONST_ME_POFF) - return false - end - return true end diff --git a/data/creaturescripts/scripts/player_death.lua b/data/creaturescripts/scripts/playerdeath.lua similarity index 82% rename from data/creaturescripts/scripts/player_death.lua rename to data/creaturescripts/scripts/playerdeath.lua index 4c849c4..943977d 100644 --- a/data/creaturescripts/scripts/player_death.lua +++ b/data/creaturescripts/scripts/playerdeath.lua @@ -1,7 +1,7 @@ local deathListEnabled = true local maxDeathRecords = 5 -function onDeath(player, corpse, killer, mostDamageKiller, unjustified, mostDamageUnjustified) +function onDeath(player, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified) local playerId = player:getId() if nextUseStaminaTime[playerId] then nextUseStaminaTime[playerId] = nil @@ -24,7 +24,7 @@ function onDeath(player, corpse, killer, mostDamageKiller, unjustified, mostDama byPlayer = 1 end end - killerName = killer:isMonster() and killer:getType():getNameDescription() or killer:getName() + killerName = killer:getName() else killerName = "field item" end @@ -41,13 +41,13 @@ function onDeath(player, corpse, killer, mostDamageKiller, unjustified, mostDama byPlayerMostDamage = 1 end end - mostDamageName = mostDamageKiller:isMonster() and mostDamageKiller:getType():getNameDescription() or mostDamageKiller:getName() + mostDamageName = mostDamageKiller:getName() else mostDamageName = "field item" end local playerGuid = player:getGuid() - db.query("INSERT INTO `player_deaths` (`player_id`, `time`, `level`, `killed_by`, `is_player`, `mostdamage_by`, `mostdamage_is_player`, `unjustified`, `mostdamage_unjustified`) VALUES (" .. playerGuid .. ", " .. os.time() .. ", " .. player:getLevel() .. ", " .. db.escapeString(killerName) .. ", " .. byPlayer .. ", " .. db.escapeString(mostDamageName) .. ", " .. byPlayerMostDamage .. ", " .. (unjustified and 1 or 0) .. ", " .. (mostDamageUnjustified and 1 or 0) .. ")") + db.query("INSERT INTO `player_deaths` (`player_id`, `time`, `level`, `killed_by`, `is_player`, `mostdamage_by`, `mostdamage_is_player`, `unjustified`, `mostdamage_unjustified`) VALUES (" .. playerGuid .. ", " .. os.time() .. ", " .. player:getLevel() .. ", " .. db.escapeString(killerName) .. ", " .. byPlayer .. ", " .. db.escapeString(mostDamageName) .. ", " .. byPlayerMostDamage .. ", " .. (lastHitUnjustified and 1 or 0) .. ", " .. (mostDamageUnjustified and 1 or 0) .. ")") local resultId = db.storeQuery("SELECT `player_id` FROM `player_deaths` WHERE `player_id` = " .. playerGuid) local deathRecords = 0 @@ -86,8 +86,4 @@ function onDeath(player, corpse, killer, mostDamageKiller, unjustified, mostDama end end end - - if killer:isPlayer() then - Game.broadcastMessage("DeathCast: "..player:getName().." ["..player:getLevel().."] just got killed by "..killer:getName().." ["..killer:getLevel().."].", MESSAGE_STATUS_DEFAULT) - end end diff --git a/data/creaturescripts/scripts/regenerate_stamina.lua b/data/creaturescripts/scripts/regeneratestamina.lua similarity index 100% rename from data/creaturescripts/scripts/regenerate_stamina.lua rename to data/creaturescripts/scripts/regeneratestamina.lua diff --git a/data/events/scripts/creature.lua b/data/events/scripts/creature.lua index e0db954..dd9a9e4 100644 --- a/data/events/scripts/creature.lua +++ b/data/events/scripts/creature.lua @@ -1,121 +1,34 @@ -__picif = {} function Creature:onChangeOutfit(outfit) - if self:isPlayer() then - if self:getStorageValue(Storage.events) > 0 then + if hasEventCallback(EVENT_CALLBACK_ONCHANGEMOUNT) then + if not EventCallback(EVENT_CALLBACK_ONCHANGEMOUNT, self, outfit.lookMount) then return false end end - return true -end - -function Creature:onAreaCombat(tile, isAggressive) - return true -end - -local function removeCombatProtection(cid) - local player = Player(cid) - if not player then - return true - end - - local time = 0 - if player:isMage() then - time = 10 - elseif player:isPaladin() then - time = 20 + if hasEventCallback(EVENT_CALLBACK_ONCHANGEOUTFIT) then + return EventCallback(EVENT_CALLBACK_ONCHANGEOUTFIT, self, outfit) else - time = 30 + return true end - - player:setStorageValue(Storage.combatProtectionStorage, 2) - addEvent(function(cid) - local player = Player(cid) - if not player then - return - end - - player:setStorageValue(Storage.combatProtectionStorage, 0) - player:remove() - end, time * 1000, cid) end -local function addStamina(name) - local player = Player(name) - if not player then - staminaBonus.events[name] = nil +function Creature:onAreaCombat(tile, isAggressive) + if hasEventCallback(EVENT_CALLBACK_ONAREACOMBAT) then + return EventCallback(EVENT_CALLBACK_ONAREACOMBAT, self, tile, isAggressive) else - local target = player:getTarget() - if not target or target:getName() ~= staminaBonus.target then - staminaBonus.events[name] = nil - else - player:setStamina(player:getStamina() + staminaBonus.bonus) - staminaBonus.events[name] = addEvent(addStamina, staminaBonus.period, name) - end + return RETURNVALUE_NOERROR end end function Creature:onTargetCombat(target) - if not self then - return true - end - - if not __picif[target.uid] then - if target:isMonster() then - target:registerEvent("RewardSystemSlogan") - __picif[target.uid] = {} - end - end - - if target:isPlayer() then - if self:isMonster() then - local protectionStorage = target:getStorageValue(Storage.combatProtectionStorage) - - if target:getIp() == 0 then -- If player is disconnected, monster shall ignore to attack the player - if protectionStorage <= 0 then - addEvent(removeCombatProtection, 30 * 1000, target.uid) - target:setStorageValue(Storage.combatProtectionStorage, 1) - elseif protectionStorage == 1 then - self:searchTarget() - return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER - end - - return true - end - - if protectionStorage >= os.time() then - return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER - end - end - end - - if PARTY_PROTECTION ~= 0 then - if self:isPlayer() and target:isPlayer() then - local party = self:getParty() - if party then - local targetParty = target:getParty() - if targetParty and targetParty == party then - return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER - end - end - end + if hasEventCallback(EVENT_CALLBACK_ONTARGETCOMBAT) then + return EventCallback(EVENT_CALLBACK_ONTARGETCOMBAT, self, target) + else + return RETURNVALUE_NOERROR end +end - if ADVANCED_SECURE_MODE ~= 0 then - if self:isPlayer() and target:isPlayer() then - if self:hasSecureMode() then - return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER - end - end - end - - -- events - if self:isPlayer() and target:isPlayer() then - if self:getStorageValue(Storage.events) > 0 then - if self:getStorageValue(Storage.events) == target:getStorageValue(Storage.events) then - return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER - end - end +function Creature:onHear(speaker, words, type) + if hasEventCallback(EVENT_CALLBACK_ONHEAR) then + EventCallback(EVENT_CALLBACK_ONHEAR, self, speaker, words, type) end - - return true end diff --git a/data/events/scripts/creature2.lua b/data/events/scripts/creature2.lua new file mode 100644 index 0000000..e0db954 --- /dev/null +++ b/data/events/scripts/creature2.lua @@ -0,0 +1,121 @@ +__picif = {} +function Creature:onChangeOutfit(outfit) + if self:isPlayer() then + if self:getStorageValue(Storage.events) > 0 then + return false + end + end + return true +end + +function Creature:onAreaCombat(tile, isAggressive) + return true +end + +local function removeCombatProtection(cid) + local player = Player(cid) + if not player then + return true + end + + local time = 0 + if player:isMage() then + time = 10 + elseif player:isPaladin() then + time = 20 + else + time = 30 + end + + player:setStorageValue(Storage.combatProtectionStorage, 2) + addEvent(function(cid) + local player = Player(cid) + if not player then + return + end + + player:setStorageValue(Storage.combatProtectionStorage, 0) + player:remove() + end, time * 1000, cid) +end + +local function addStamina(name) + local player = Player(name) + if not player then + staminaBonus.events[name] = nil + else + local target = player:getTarget() + if not target or target:getName() ~= staminaBonus.target then + staminaBonus.events[name] = nil + else + player:setStamina(player:getStamina() + staminaBonus.bonus) + staminaBonus.events[name] = addEvent(addStamina, staminaBonus.period, name) + end + end +end + +function Creature:onTargetCombat(target) + if not self then + return true + end + + if not __picif[target.uid] then + if target:isMonster() then + target:registerEvent("RewardSystemSlogan") + __picif[target.uid] = {} + end + end + + if target:isPlayer() then + if self:isMonster() then + local protectionStorage = target:getStorageValue(Storage.combatProtectionStorage) + + if target:getIp() == 0 then -- If player is disconnected, monster shall ignore to attack the player + if protectionStorage <= 0 then + addEvent(removeCombatProtection, 30 * 1000, target.uid) + target:setStorageValue(Storage.combatProtectionStorage, 1) + elseif protectionStorage == 1 then + self:searchTarget() + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER + end + + return true + end + + if protectionStorage >= os.time() then + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER + end + end + end + + if PARTY_PROTECTION ~= 0 then + if self:isPlayer() and target:isPlayer() then + local party = self:getParty() + if party then + local targetParty = target:getParty() + if targetParty and targetParty == party then + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER + end + end + end + end + + if ADVANCED_SECURE_MODE ~= 0 then + if self:isPlayer() and target:isPlayer() then + if self:hasSecureMode() then + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER + end + end + end + + -- events + if self:isPlayer() and target:isPlayer() then + if self:getStorageValue(Storage.events) > 0 then + if self:getStorageValue(Storage.events) == target:getStorageValue(Storage.events) then + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER + end + end + end + + return true +end diff --git a/data/events/scripts/party.lua b/data/events/scripts/party.lua index bbb4cfa..9ffdc07 100644 --- a/data/events/scripts/party.lua +++ b/data/events/scripts/party.lua @@ -1,11 +1,49 @@ function Party:onJoin(player) - return true + if hasEventCallback(EVENT_CALLBACK_ONJOIN) then + return EventCallback(EVENT_CALLBACK_ONJOIN, self, player) + else + return true + end end function Party:onLeave(player) - return true + if hasEventCallback(EVENT_CALLBACK_ONLEAVE) then + return EventCallback(EVENT_CALLBACK_ONLEAVE, self, player) + else + return true + end end function Party:onDisband() - return true + if hasEventCallback(EVENT_CALLBACK_ONDISBAND) then + return EventCallback(EVENT_CALLBACK_ONDISBAND, self) + else + return true + end +end + +function Party:onShareExperience(exp) + local sharedExperienceMultiplier = 1.20 --20% + local vocationsIds = {} + local rawExp = exp + + local vocationId = self:getLeader():getVocation():getBase():getId() + if vocationId ~= VOCATION_NONE then + table.insert(vocationsIds, vocationId) + end + + for _, member in ipairs(self:getMembers()) do + vocationId = member:getVocation():getBase():getId() + if not table.contains(vocationsIds, vocationId) and vocationId ~= VOCATION_NONE then + table.insert(vocationsIds, vocationId) + end + end + + local size = #vocationsIds + if size > 1 then + sharedExperienceMultiplier = 1.0 + ((size * (5 * (size - 1) + 10)) / 100) + end + + exp = math.ceil((exp * sharedExperienceMultiplier) / (#self:getMembers() + 1)) + return hasEventCallback(EVENT_CALLBACK_ONSHAREEXPERIENCE) and EventCallback(EVENT_CALLBACK_ONSHAREEXPERIENCE, self, exp, rawExp) or exp end diff --git a/data/events/scripts/player.lua b/data/events/scripts/player.lua index c26f314..244ccf9 100644 --- a/data/events/scripts/player.lua +++ b/data/events/scripts/player.lua @@ -1,311 +1,95 @@ --- Internal Use --- EXAMPLE = 26052 - --- No move items with actionID 8000 --- Players cannot throw items on teleports if set to true -local blockTeleportTrashing = true - -local function getHours(seconds) - return math.floor((seconds/60)/60) -end - -local function getMinutes(seconds) - return math.floor(seconds/60) -end - -local function getTime(seconds) - local hours, minutes = getHours(seconds), getMinutes(seconds) - if (minutes > 59) then - minutes = minutes-hours*60 - end - - if (minutes < 10) then - minutes = "0" ..minutes - end - - return hours..":"..minutes.. "h" -end - function Player:onLook(thing, position, distance) - local description = "You see " .. thing:getDescription(distance) - if self:getGroup():getAccess() then - if thing:isItem() then - description = string.format("%s\nItem ID: %d", description, thing:getId()) - - local actionId = thing:getActionId() - if actionId ~= 0 then - description = string.format("%s, Action ID: %d", description, actionId) - end - - local uniqueId = thing:getAttribute(ITEM_ATTRIBUTE_UNIQUEID) - if uniqueId > 0 and uniqueId < 65536 then - description = string.format("%s, Unique ID: %d", description, uniqueId) - end - - local itemType = thing:getType() - - local transformEquipId = itemType:getTransformEquipId() - local transformDeEquipId = itemType:getTransformDeEquipId() - if transformEquipId ~= 0 then - description = string.format("%s\nTransforms to: %d (onEquip)", description, transformEquipId) - elseif transformDeEquipId ~= 0 then - description = string.format("%s\nTransforms to: %d (onDeEquip)", description, transformDeEquipId) - end - - local decayId = itemType:getDecayId() - if decayId ~= -1 then - description = string.format("%s\nDecays to: %d", description, decayId) - end - elseif thing:isCreature() then - local str = "%s\nHealth: %d / %d" - if thing:isPlayer() and thing:getMaxMana() > 0 then - str = string.format("%s, Mana: %d / %d", str, thing:getMana(), thing:getMaxMana()) - end - description = string.format(str, description, thing:getHealth(), thing:getMaxHealth()) .. "." - end - - local position = thing:getPosition() - description = string.format( - "%s\nPosition: %d, %d, %d", - description, position.x, position.y, position.z - ) - - if thing:isCreature() then - if thing:isPlayer() then - description = string.format("%s\nIP: %s.", description, Game.convertIpToString(thing:getIp())) - end - end + local description = "" + if hasEventCallback(EVENT_CALLBACK_ONLOOK) then + description = EventCallback(EVENT_CALLBACK_ONLOOK, self, thing, position, distance, description) end self:sendTextMessage(MESSAGE_INFO_DESCR, description) end function Player:onLookInBattleList(creature, distance) - local description = "You see " .. creature:getDescription(distance) - if self:getGroup():getAccess() then - local str = "%s\nHealth: %d / %d" - if creature:isPlayer() and creature:getMaxMana() > 0 then - str = string.format("%s, Mana: %d / %d", str, creature:getMana(), creature:getMaxMana()) - end - description = string.format(str, description, creature:getHealth(), creature:getMaxHealth()) .. "." - - local position = creature:getPosition() - description = string.format( - "%s\nPosition: %d, %d, %d", - description, position.x, position.y, position.z - ) - - if creature:isPlayer() then - description = string.format("%s\nIP: %s", description, Game.convertIpToString(creature:getIp())) - end + local description = "" + if hasEventCallback(EVENT_CALLBACK_ONLOOKINBATTLELIST) then + description = EventCallback(EVENT_CALLBACK_ONLOOKINBATTLELIST, self, creature, distance, description) end self:sendTextMessage(MESSAGE_INFO_DESCR, description) end function Player:onLookInTrade(partner, item, distance) - self:sendTextMessage(MESSAGE_INFO_DESCR, "You see " .. item:getDescription(distance)) + local description = "You see " .. item:getDescription(distance) + if hasEventCallback(EVENT_CALLBACK_ONLOOKINTRADE) then + description = EventCallback(EVENT_CALLBACK_ONLOOKINTRADE, self, partner, item, distance, description) + end + self:sendTextMessage(MESSAGE_INFO_DESCR, description) end -function Player:onLookInShop(itemType, count) - return true +function Player:onLookInShop(itemType, count, description) + local description = "You see " .. description + if hasEventCallback(EVENT_CALLBACK_ONLOOKINSHOP) then + description = EventCallback(EVENT_CALLBACK_ONLOOKINSHOP, self, itemType, count, description) + end + self:sendTextMessage(MESSAGE_INFO_DESCR, description) end function Player:onMoveItem(item, count, fromPosition, toPosition, fromCylinder, toCylinder) - -- No move items with actionID 8000 - if item:getActionId() == NOT_MOVEABLE_ACTION then - self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) - return false - end - - -- Check two-handed weapons - if toPosition.x ~= CONTAINER_POSITION then - return true - end - - if item:getTopParent() == self and bit.band(toPosition.y, 0x40) == 0 then - local itemType, moveItem = ItemType(item:getId()) - if bit.band(itemType:getSlotPosition(), SLOTP_TWO_HAND) ~= 0 and toPosition.y == CONST_SLOT_LEFT then - moveItem = self:getSlotItem(CONST_SLOT_RIGHT) - elseif itemType:getWeaponType() == WEAPON_SHIELD and toPosition.y == CONST_SLOT_RIGHT then - moveItem = self:getSlotItem(CONST_SLOT_LEFT) - if moveItem and bit.band(ItemType(moveItem:getId()):getSlotPosition(), SLOTP_TWO_HAND) == 0 then - return true - end - end - - if moveItem then - local parent = item:getParent() - if parent:isContainer() and parent:getSize() == parent:getCapacity() then - self:sendTextMessage(MESSAGE_STATUS_SMALL, Game.getReturnMessage(RETURNVALUE_CONTAINERNOTENOUGHROOM)) - return false - else - return moveItem:moveTo(parent) - end - end - end - - -- Reward System - if toPosition.x == CONTAINER_POSITION then - local containerId = toPosition.y - 64 - local container = self:getContainerById(containerId) - if not container then - return true - end - - -- Do not let the player insert items into either the Reward Container or the Reward Chest - local itemId = container:getId() - if itemId == ITEM_REWARD_CONTAINER or itemId == ITEM_REWARD_CHEST then - self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) - return false - end - - -- The player also shouldn't be able to insert items into the boss corpse - local tile = Tile(container:getPosition()) - for _, item in ipairs(tile:getItems() or { }) do - if item:getAttribute(ITEM_ATTRIBUTE_CORPSEOWNER) == 2^31 - 1 and item:getName() == container:getName() then - self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) - return false - end - end - end - - -- Do not let the player move the boss corpse. - if item:getAttribute(ITEM_ATTRIBUTE_CORPSEOWNER) == 2^31 - 1 then - self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) - return false - end - - -- Players cannot throw items on reward chest - local tile = Tile(toPosition) - if tile and tile:getItemById(ITEM_REWARD_CHEST) then - self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) - self:getPosition():sendMagicEffect(CONST_ME_POFF) - return false - end - - -- Players cannot throw items on teleports - if blockTeleportTrashing and toPosition.x ~= CONTAINER_POSITION then - local thing = Tile(toPosition):getItemByType(ITEM_TYPE_TELEPORT) - if thing then - self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) - self:getPosition():sendMagicEffect(CONST_ME_POFF) - return false - end - end - - --[[-- Do not stop trying this test - -- No move parcel very heavy - if item:getWeight() > 90000 and item:getId() == ITEM_PARCEL then - self:sendCancelMessage('YOU CANNOT MOVE PARCELS TOO HEAVY.') - return false - end - - -- No move if item count > 26 items - if tile and tile:getItemCount() > 26 then - self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) - return false + if hasEventCallback(EVENT_CALLBACK_ONMOVEITEM) then + return EventCallback(EVENT_CALLBACK_ONMOVEITEM, self, item, count, fromPosition, toPosition, fromCylinder, toCylinder) end - - if tile and tile:getItemById(370) then -- Trapdoor - self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) - self:getPosition():sendMagicEffect(CONST_ME_POFF) - return false - end ]] return true end function Player:onItemMoved(item, count, fromPosition, toPosition, fromCylinder, toCylinder) + if hasEventCallback(EVENT_CALLBACK_ONITEMMOVED) then + EventCallback(EVENT_CALLBACK_ONITEMMOVED, self, item, count, fromPosition, toPosition, fromCylinder, toCylinder) + end end function Player:onMoveCreature(creature, fromPosition, toPosition) - return true -end - -local function hasPendingReport(name, targetName, reportType) - local f = io.open(string.format("data/reports/players/%s-%s-%d.txt", name, targetName, reportType), "r") - if f then - io.close(f) - return true - else - return false + if hasEventCallback(EVENT_CALLBACK_ONMOVECREATURE) then + return EventCallback(EVENT_CALLBACK_ONMOVECREATURE, self, creature, fromPosition, toPosition) end + return true end function Player:onReportRuleViolation(targetName, reportType, reportReason, comment, translation) - local name = self:getName() - if hasPendingReport(name, targetName, reportType) then - self:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your report is being processed.") - return - end - - local file = io.open(string.format("data/reports/players/%s-%s-%d.txt", name, targetName, reportType), "a") - if not file then - self:sendTextMessage(MESSAGE_EVENT_ADVANCE, "There was an error when processing your report, please contact a gamemaster.") - return + if hasEventCallback(EVENT_CALLBACK_ONREPORTRULEVIOLATION) then + EventCallback(EVENT_CALLBACK_ONREPORTRULEVIOLATION, self, targetName, reportType, reportReason, comment, translation) end - - io.output(file) - io.write("------------------------------\n") - io.write("Reported by: " .. name .. "\n") - io.write("Target: " .. targetName .. "\n") - io.write("Type: " .. reportType .. "\n") - io.write("Reason: " .. reportReason .. "\n") - io.write("Comment: " .. comment .. "\n") - if reportType ~= REPORT_TYPE_BOT then - io.write("Translation: " .. translation .. "\n") - end - io.write("------------------------------\n") - io.close(file) - self:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("Thank you for reporting %s. Your report will be processed by %s team as soon as possible.", targetName, configManager.getString(configKeys.SERVER_NAME))) - return end function Player:onReportBug(message, position, category) - if self:getAccountType() == ACCOUNT_TYPE_NORMAL then - return false + if hasEventCallback(EVENT_CALLBACK_ONREPORTBUG) then + return EventCallback(EVENT_CALLBACK_ONREPORTBUG, self, message, position, category) end - - local name = self:getName() - local file = io.open("data/reports/bugs/" .. name .. " report.txt", "a") - - if not file then - self:sendTextMessage(MESSAGE_EVENT_DEFAULT, "There was an error when processing your report, please contact a gamemaster.") - return true - end - - io.output(file) - io.write("------------------------------\n") - io.write("Name: " .. name) - if category == BUG_CATEGORY_MAP then - io.write(" [Map position: " .. position.x .. ", " .. position.y .. ", " .. position.z .. "]") - end - local playerPosition = self:getPosition() - io.write(" [Player Position: " .. playerPosition.x .. ", " .. playerPosition.y .. ", " .. playerPosition.z .. "]\n") - io.write("Comment: " .. message .. "\n") - io.close(file) - - self:sendTextMessage(MESSAGE_EVENT_DEFAULT, "Your report has been sent to " .. configManager.getString(configKeys.SERVER_NAME) .. ".") return true end function Player:onTurn(direction) - if self:getGroup():getAccess() and self:getDirection() == direction then - local nextPosition = self:getPosition() - nextPosition:getNextPosition(direction) - - self:teleportTo(nextPosition, true) + if hasEventCallback(EVENT_CALLBACK_ONTURN) then + return EventCallback(EVENT_CALLBACK_ONTURN, self, direction) end - return true end function Player:onTradeRequest(target, item) + if hasEventCallback(EVENT_CALLBACK_ONTRADEREQUEST) then + return EventCallback(EVENT_CALLBACK_ONTRADEREQUEST, self, target, item) + end return true end function Player:onTradeAccept(target, item, targetItem) + if hasEventCallback(EVENT_CALLBACK_ONTRADEACCEPT) then + return EventCallback(EVENT_CALLBACK_ONTRADEACCEPT, self, target, item, targetItem) + end return true end +function Player:onTradeCompleted(target, item, targetItem, isSuccess) + if hasEventCallback(EVENT_CALLBACK_ONTRADECOMPLETED) then + EventCallback(EVENT_CALLBACK_ONTRADECOMPLETED, self, target, item, targetItem, isSuccess) + end +end + local soulCondition = Condition(CONDITION_SOUL, CONDITIONID_DEFAULT) soulCondition:setTicks(4 * 60 * 1000) soulCondition:setParameter(CONDITION_PARAM_SOULGAIN, 1) @@ -337,67 +121,6 @@ local function useStamina(player) player:setStamina(staminaMinutes) end -local function useStaminaXp(player) - local staminaMinutes = player:getExpBoostStamina() - if staminaMinutes == 0 then - return - end - - local playerId = player:getId() - local currentTime = os.time() - local timePassed = currentTime - nextUseXpStamina[playerId] - if timePassed <= 0 then - return - end - - if timePassed > 60 then - if staminaMinutes > 2 then - staminaMinutes = staminaMinutes - 2 - else - staminaMinutes = 0 - end - nextUseXpStamina[playerId] = currentTime + 120 - else - staminaMinutes = staminaMinutes - 1 - nextUseXpStamina[playerId] = currentTime + 60 - end - player:setExpBoostStamina(staminaMinutes) -end - -function Player:onUseWeapon(normalDamage, elementType, elementDamage) - local weapon = self:getSlotItem(CONST_SLOT_LEFT) - if not weapon or weapon:getType():getWeaponType() == WEAPON_SHIELD then - weapon = self:getSlotItem(CONST_SLOT_RIGHT) - end - - -- Imbuement - if (weapon and weapon:getType():getImbuingSlots() > 0) then - for i = 1, weapon:getType():getImbuingSlots() do - local slotEnchant = weapon:getSpecialAttribute(i) - if (slotEnchant) then - local percentDamage, enchantPercent = 0, weapon:getImbuementPercent(slotEnchant) - local typeEnchant = weapon:getImbuementType(i) or "" - if (typeEnchant ~= "") then - useStaminaImbuing(self:getId(), weapon:getUniqueId()) - end - - if (typeEnchant == "firedamage") then - elementType = COMBAT_FIREDAMAGE - elseif (typeEnchant == "earthdamage") then - elementType = COMBAT_EARTHDAMAGE - elseif (typeEnchant == "icedamage") then - elementType = COMBAT_ICEDAMAGE - elseif (typeEnchant == "energydamage") then - elementType = COMBAT_ENERGYDAMAGE - elseif (typeEnchant == "deathdamage") then - elementType = COMBAT_DEATHDAMAGE - end - end - end - end - return normalDamage, elementType, elementDamage -end - function Player:onGainExperience(source, exp, rawExp) if not source or source:isPlayer() then return exp @@ -425,20 +148,22 @@ function Player:onGainExperience(source, exp, rawExp) end end - return exp + return hasEventCallback(EVENT_CALLBACK_ONGAINEXPERIENCE) and EventCallback(EVENT_CALLBACK_ONGAINEXPERIENCE, self, source, exp, rawExp) or exp end function Player:onLoseExperience(exp) - return exp + return hasEventCallback(EVENT_CALLBACK_ONLOSEEXPERIENCE) and EventCallback(EVENT_CALLBACK_ONLOSEEXPERIENCE, self, exp) or exp end function Player:onGainSkillTries(skill, tries) if APPLY_SKILL_MULTIPLIER == false then - return tries + return hasEventCallback(EVENT_CALLBACK_ONGAINSKILLTRIES) and EventCallback(EVENT_CALLBACK_ONGAINSKILLTRIES, self, skill, tries) or tries end if skill == SKILL_MAGLEVEL then - return tries * configManager.getNumber(configKeys.RATE_MAGIC) + tries = tries * configManager.getNumber(configKeys.RATE_MAGIC) + return hasEventCallback(EVENT_CALLBACK_ONGAINSKILLTRIES) and EventCallback(EVENT_CALLBACK_ONGAINSKILLTRIES, self, skill, tries) or tries end - return tries * configManager.getNumber(configKeys.RATE_SKILL) + tries = tries * configManager.getNumber(configKeys.RATE_SKILL) + return hasEventCallback(EVENT_CALLBACK_ONGAINSKILLTRIES) and EventCallback(EVENT_CALLBACK_ONGAINSKILLTRIES, self, skill, tries) or tries end diff --git a/data/events/scripts/player2.lua b/data/events/scripts/player2.lua new file mode 100644 index 0000000..c26f314 --- /dev/null +++ b/data/events/scripts/player2.lua @@ -0,0 +1,444 @@ +-- Internal Use +-- EXAMPLE = 26052 + +-- No move items with actionID 8000 +-- Players cannot throw items on teleports if set to true +local blockTeleportTrashing = true + +local function getHours(seconds) + return math.floor((seconds/60)/60) +end + +local function getMinutes(seconds) + return math.floor(seconds/60) +end + +local function getTime(seconds) + local hours, minutes = getHours(seconds), getMinutes(seconds) + if (minutes > 59) then + minutes = minutes-hours*60 + end + + if (minutes < 10) then + minutes = "0" ..minutes + end + + return hours..":"..minutes.. "h" +end + +function Player:onLook(thing, position, distance) + local description = "You see " .. thing:getDescription(distance) + if self:getGroup():getAccess() then + if thing:isItem() then + description = string.format("%s\nItem ID: %d", description, thing:getId()) + + local actionId = thing:getActionId() + if actionId ~= 0 then + description = string.format("%s, Action ID: %d", description, actionId) + end + + local uniqueId = thing:getAttribute(ITEM_ATTRIBUTE_UNIQUEID) + if uniqueId > 0 and uniqueId < 65536 then + description = string.format("%s, Unique ID: %d", description, uniqueId) + end + + local itemType = thing:getType() + + local transformEquipId = itemType:getTransformEquipId() + local transformDeEquipId = itemType:getTransformDeEquipId() + if transformEquipId ~= 0 then + description = string.format("%s\nTransforms to: %d (onEquip)", description, transformEquipId) + elseif transformDeEquipId ~= 0 then + description = string.format("%s\nTransforms to: %d (onDeEquip)", description, transformDeEquipId) + end + + local decayId = itemType:getDecayId() + if decayId ~= -1 then + description = string.format("%s\nDecays to: %d", description, decayId) + end + elseif thing:isCreature() then + local str = "%s\nHealth: %d / %d" + if thing:isPlayer() and thing:getMaxMana() > 0 then + str = string.format("%s, Mana: %d / %d", str, thing:getMana(), thing:getMaxMana()) + end + description = string.format(str, description, thing:getHealth(), thing:getMaxHealth()) .. "." + end + + local position = thing:getPosition() + description = string.format( + "%s\nPosition: %d, %d, %d", + description, position.x, position.y, position.z + ) + + if thing:isCreature() then + if thing:isPlayer() then + description = string.format("%s\nIP: %s.", description, Game.convertIpToString(thing:getIp())) + end + end + end + self:sendTextMessage(MESSAGE_INFO_DESCR, description) +end + +function Player:onLookInBattleList(creature, distance) + local description = "You see " .. creature:getDescription(distance) + if self:getGroup():getAccess() then + local str = "%s\nHealth: %d / %d" + if creature:isPlayer() and creature:getMaxMana() > 0 then + str = string.format("%s, Mana: %d / %d", str, creature:getMana(), creature:getMaxMana()) + end + description = string.format(str, description, creature:getHealth(), creature:getMaxHealth()) .. "." + + local position = creature:getPosition() + description = string.format( + "%s\nPosition: %d, %d, %d", + description, position.x, position.y, position.z + ) + + if creature:isPlayer() then + description = string.format("%s\nIP: %s", description, Game.convertIpToString(creature:getIp())) + end + end + self:sendTextMessage(MESSAGE_INFO_DESCR, description) +end + +function Player:onLookInTrade(partner, item, distance) + self:sendTextMessage(MESSAGE_INFO_DESCR, "You see " .. item:getDescription(distance)) +end + +function Player:onLookInShop(itemType, count) + return true +end + +function Player:onMoveItem(item, count, fromPosition, toPosition, fromCylinder, toCylinder) + -- No move items with actionID 8000 + if item:getActionId() == NOT_MOVEABLE_ACTION then + self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return false + end + + -- Check two-handed weapons + if toPosition.x ~= CONTAINER_POSITION then + return true + end + + if item:getTopParent() == self and bit.band(toPosition.y, 0x40) == 0 then + local itemType, moveItem = ItemType(item:getId()) + if bit.band(itemType:getSlotPosition(), SLOTP_TWO_HAND) ~= 0 and toPosition.y == CONST_SLOT_LEFT then + moveItem = self:getSlotItem(CONST_SLOT_RIGHT) + elseif itemType:getWeaponType() == WEAPON_SHIELD and toPosition.y == CONST_SLOT_RIGHT then + moveItem = self:getSlotItem(CONST_SLOT_LEFT) + if moveItem and bit.band(ItemType(moveItem:getId()):getSlotPosition(), SLOTP_TWO_HAND) == 0 then + return true + end + end + + if moveItem then + local parent = item:getParent() + if parent:isContainer() and parent:getSize() == parent:getCapacity() then + self:sendTextMessage(MESSAGE_STATUS_SMALL, Game.getReturnMessage(RETURNVALUE_CONTAINERNOTENOUGHROOM)) + return false + else + return moveItem:moveTo(parent) + end + end + end + + -- Reward System + if toPosition.x == CONTAINER_POSITION then + local containerId = toPosition.y - 64 + local container = self:getContainerById(containerId) + if not container then + return true + end + + -- Do not let the player insert items into either the Reward Container or the Reward Chest + local itemId = container:getId() + if itemId == ITEM_REWARD_CONTAINER or itemId == ITEM_REWARD_CHEST then + self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return false + end + + -- The player also shouldn't be able to insert items into the boss corpse + local tile = Tile(container:getPosition()) + for _, item in ipairs(tile:getItems() or { }) do + if item:getAttribute(ITEM_ATTRIBUTE_CORPSEOWNER) == 2^31 - 1 and item:getName() == container:getName() then + self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return false + end + end + end + + -- Do not let the player move the boss corpse. + if item:getAttribute(ITEM_ATTRIBUTE_CORPSEOWNER) == 2^31 - 1 then + self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return false + end + + -- Players cannot throw items on reward chest + local tile = Tile(toPosition) + if tile and tile:getItemById(ITEM_REWARD_CHEST) then + self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + self:getPosition():sendMagicEffect(CONST_ME_POFF) + return false + end + + -- Players cannot throw items on teleports + if blockTeleportTrashing and toPosition.x ~= CONTAINER_POSITION then + local thing = Tile(toPosition):getItemByType(ITEM_TYPE_TELEPORT) + if thing then + self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + self:getPosition():sendMagicEffect(CONST_ME_POFF) + return false + end + end + + --[[-- Do not stop trying this test + -- No move parcel very heavy + if item:getWeight() > 90000 and item:getId() == ITEM_PARCEL then + self:sendCancelMessage('YOU CANNOT MOVE PARCELS TOO HEAVY.') + return false + end + + -- No move if item count > 26 items + if tile and tile:getItemCount() > 26 then + self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return false + end + + if tile and tile:getItemById(370) then -- Trapdoor + self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + self:getPosition():sendMagicEffect(CONST_ME_POFF) + return false + end ]] + return true +end + +function Player:onItemMoved(item, count, fromPosition, toPosition, fromCylinder, toCylinder) +end + +function Player:onMoveCreature(creature, fromPosition, toPosition) + return true +end + +local function hasPendingReport(name, targetName, reportType) + local f = io.open(string.format("data/reports/players/%s-%s-%d.txt", name, targetName, reportType), "r") + if f then + io.close(f) + return true + else + return false + end +end + +function Player:onReportRuleViolation(targetName, reportType, reportReason, comment, translation) + local name = self:getName() + if hasPendingReport(name, targetName, reportType) then + self:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your report is being processed.") + return + end + + local file = io.open(string.format("data/reports/players/%s-%s-%d.txt", name, targetName, reportType), "a") + if not file then + self:sendTextMessage(MESSAGE_EVENT_ADVANCE, "There was an error when processing your report, please contact a gamemaster.") + return + end + + io.output(file) + io.write("------------------------------\n") + io.write("Reported by: " .. name .. "\n") + io.write("Target: " .. targetName .. "\n") + io.write("Type: " .. reportType .. "\n") + io.write("Reason: " .. reportReason .. "\n") + io.write("Comment: " .. comment .. "\n") + if reportType ~= REPORT_TYPE_BOT then + io.write("Translation: " .. translation .. "\n") + end + io.write("------------------------------\n") + io.close(file) + self:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("Thank you for reporting %s. Your report will be processed by %s team as soon as possible.", targetName, configManager.getString(configKeys.SERVER_NAME))) + return +end + +function Player:onReportBug(message, position, category) + if self:getAccountType() == ACCOUNT_TYPE_NORMAL then + return false + end + + local name = self:getName() + local file = io.open("data/reports/bugs/" .. name .. " report.txt", "a") + + if not file then + self:sendTextMessage(MESSAGE_EVENT_DEFAULT, "There was an error when processing your report, please contact a gamemaster.") + return true + end + + io.output(file) + io.write("------------------------------\n") + io.write("Name: " .. name) + if category == BUG_CATEGORY_MAP then + io.write(" [Map position: " .. position.x .. ", " .. position.y .. ", " .. position.z .. "]") + end + local playerPosition = self:getPosition() + io.write(" [Player Position: " .. playerPosition.x .. ", " .. playerPosition.y .. ", " .. playerPosition.z .. "]\n") + io.write("Comment: " .. message .. "\n") + io.close(file) + + self:sendTextMessage(MESSAGE_EVENT_DEFAULT, "Your report has been sent to " .. configManager.getString(configKeys.SERVER_NAME) .. ".") + return true +end + +function Player:onTurn(direction) + if self:getGroup():getAccess() and self:getDirection() == direction then + local nextPosition = self:getPosition() + nextPosition:getNextPosition(direction) + + self:teleportTo(nextPosition, true) + end + + return true +end + +function Player:onTradeRequest(target, item) + return true +end + +function Player:onTradeAccept(target, item, targetItem) + return true +end + +local soulCondition = Condition(CONDITION_SOUL, CONDITIONID_DEFAULT) +soulCondition:setTicks(4 * 60 * 1000) +soulCondition:setParameter(CONDITION_PARAM_SOULGAIN, 1) + +local function useStamina(player) + local staminaMinutes = player:getStamina() + if staminaMinutes == 0 then + return + end + + local playerId = player:getId() + local currentTime = os.time() + local timePassed = currentTime - nextUseStaminaTime[playerId] + if timePassed <= 0 then + return + end + + if timePassed > 60 then + if staminaMinutes > 2 then + staminaMinutes = staminaMinutes - 2 + else + staminaMinutes = 0 + end + nextUseStaminaTime[playerId] = currentTime + 120 + else + staminaMinutes = staminaMinutes - 1 + nextUseStaminaTime[playerId] = currentTime + 60 + end + player:setStamina(staminaMinutes) +end + +local function useStaminaXp(player) + local staminaMinutes = player:getExpBoostStamina() + if staminaMinutes == 0 then + return + end + + local playerId = player:getId() + local currentTime = os.time() + local timePassed = currentTime - nextUseXpStamina[playerId] + if timePassed <= 0 then + return + end + + if timePassed > 60 then + if staminaMinutes > 2 then + staminaMinutes = staminaMinutes - 2 + else + staminaMinutes = 0 + end + nextUseXpStamina[playerId] = currentTime + 120 + else + staminaMinutes = staminaMinutes - 1 + nextUseXpStamina[playerId] = currentTime + 60 + end + player:setExpBoostStamina(staminaMinutes) +end + +function Player:onUseWeapon(normalDamage, elementType, elementDamage) + local weapon = self:getSlotItem(CONST_SLOT_LEFT) + if not weapon or weapon:getType():getWeaponType() == WEAPON_SHIELD then + weapon = self:getSlotItem(CONST_SLOT_RIGHT) + end + + -- Imbuement + if (weapon and weapon:getType():getImbuingSlots() > 0) then + for i = 1, weapon:getType():getImbuingSlots() do + local slotEnchant = weapon:getSpecialAttribute(i) + if (slotEnchant) then + local percentDamage, enchantPercent = 0, weapon:getImbuementPercent(slotEnchant) + local typeEnchant = weapon:getImbuementType(i) or "" + if (typeEnchant ~= "") then + useStaminaImbuing(self:getId(), weapon:getUniqueId()) + end + + if (typeEnchant == "firedamage") then + elementType = COMBAT_FIREDAMAGE + elseif (typeEnchant == "earthdamage") then + elementType = COMBAT_EARTHDAMAGE + elseif (typeEnchant == "icedamage") then + elementType = COMBAT_ICEDAMAGE + elseif (typeEnchant == "energydamage") then + elementType = COMBAT_ENERGYDAMAGE + elseif (typeEnchant == "deathdamage") then + elementType = COMBAT_DEATHDAMAGE + end + end + end + end + return normalDamage, elementType, elementDamage +end + +function Player:onGainExperience(source, exp, rawExp) + if not source or source:isPlayer() then + return exp + end + + -- Soul regeneration + local vocation = self:getVocation() + if self:getSoul() < vocation:getMaxSoul() and exp >= self:getLevel() then + soulCondition:setParameter(CONDITION_PARAM_SOULTICKS, vocation:getSoulGainTicks() * 1000) + self:addCondition(soulCondition) + end + + -- Apply experience stage multiplier + exp = exp * Game.getExperienceStage(self:getLevel()) + + -- Stamina modifier + if configManager.getBoolean(configKeys.STAMINA_SYSTEM) then + useStamina(self) + + local staminaMinutes = self:getStamina() + if staminaMinutes > 2400 then + exp = exp * 1.5 + elseif staminaMinutes <= 840 then + exp = exp * 0.5 + end + end + + return exp +end + +function Player:onLoseExperience(exp) + return exp +end + +function Player:onGainSkillTries(skill, tries) + if APPLY_SKILL_MULTIPLIER == false then + return tries + end + + if skill == SKILL_MAGLEVEL then + return tries * configManager.getNumber(configKeys.RATE_MAGIC) + end + return tries * configManager.getNumber(configKeys.RATE_SKILL) +end diff --git a/data/global.lua b/data/global.lua index ee37c95..14bd2ff 100644 --- a/data/global.lua +++ b/data/global.lua @@ -1,52 +1,102 @@ math.randomseed(os.time()) dofile('data/lib/lib.lua') -NOT_MOVEABLE_ACTION = 8000 -PARTY_PROTECTION = 0 -- Set to 0 to disable. -ADVANCED_SECURE_MODE = 1 -- Set to 0 to disable. +ropeSpots = { + 384, 418, 8278, 8592, 13189, 14435, 14436, 14857, 15635, 19518, 24621, 24622, 24623, 24624, 26019 +} -ropeSpots = {384, 418, 8278, 8592} +keys = { + 2086, 2087, 2088, 2089, 2090, 2091, 2092, 10032 +} -doors = {[1209] = 1211, [1210] = 1211, [1212] = 1214, [1213] = 1214, [1219] = 1220, [1221] = 1222, [1231] = 1233, [1232] = 1233, [1234] = 1236, [1235] = 1236, [1237] = 1238, [1239] = 1240, [1249] = 1251, [1250] = 1251, [1252] = 1254, [1253] = 1254, [1539] = 1540, [1541] = 1542, [3535] = 3537, [3536] = 3537, [3538] = 3539, [3544] = 3546, [3545] = 3546, [3547] = 3548, [4913] = 4915, [4914] = 4915, [4916] = 4918, [4917] = 4918, [5082] = 5083, [5084] = 5085, [5098] = 5100, [5099] = 5100, [5101] = 5102, [5107] = 5109, [5108] = 5109, [5110] = 5111, [5116] = 5118, [5117] = 5118, [5119] = 5120, [5125] = 5127, [5126] = 5127, [5128] = 5129, [5134] = 5136, [5135] = 5136, [5137] = 5139, [5138] = 5139, [5140] = 5142, [5141] = 5142, [5143] = 5145, [5144] = 5145, [5278] = 5280, [5279] = 5280, [5281] = 5283, [5282] = 5283, [5284] = 5285, [5286] = 5287, [5515] = 5516, [5517] = 5518, [5732] = 5734, [5733] = 5734, [5735] = 5737, [5736] = 5737, [6192] = 6194, [6193] = 6194, [6195] = 6197, [6196] = 6197, [6198] = 6199, [6200] = 6201, [6249] = 6251, [6250] = 6251, [6252] = 6254, [6253] = 6254, [6255] = 6256, [6257] = 6258, [6795] = 6796, [6797] = 6798, [6799] = 6800, [6801] = 6802, [6891] = 6893, [6892] = 6893, [6894] = 6895, [6900] = 6902, [6901] = 6902, [6903] = 6904, [7033] = 7035, [7034] = 7035, [7036] = 7037, [7042] = 7044, [7043] = 7044, [7045] = 7046, [7054] = 7055, [7056] = 7057, [8541] = 8543, [8542] = 8543, [8544] = 8546, [8545] = 8546, [8547] = 8548, [8549] = 8550, [9165] = 9167, [9166] = 9167, [9168] = 9170, [9169] = 9170, [9171] = 9172, [9173] = 9174, [9267] = 9269, [9268] = 9269, [9270] = 9272, [9271] = 9272, [9273] = 9274, [9275] = 9276, [10276] = 10277, [10274] = 10275, [10268] = 10270, [10269] = 10270, [10271] = 10273, [10272] = 10273, [10471] = 10472, [10480] = 10481, [10477] = 10479, [10478] = 10479, [10468] = 10470, [10469] = 10470, [10775] = 10777, [10776] = 10777, [12092] = 12094, [12093] = 12094, [12188] = 12190, [12189] = 12190, [19840] = 19842, [19841] = 19842, [19843] = 19844, [19980] = 19982, [19981] = 19982, [19983] = 19984, [20273] = 20275, [20274] = 20275, [20276] = 20277, [17235] = 17236, [18208] = 18209, [13022] = 13023, [10784] = 10786, [10785] = 10786, [12099] = 12101, [12100] = 12101, [12197] = 12199, [12198] = 12199, [19849] = 19851, [19850] = 19851, [19852] = 19853, [19989] = 19991, [19990] = 19991, [19992] = 19993, [20282] = 20284, [20283] = 20284, [20285] = 20286, [17237] = 17238, [13020] = 13021, [10780] = 10781, [12095] = 12096, [12195] = 12196, [19845] = 19846, [19985] = 19986, [20278] = 20279, [10789] = 10790, [12102] = 12103, [12204] = 12205, [19854] = 19855, [19994] = 19995, [20287] = 20288, [10782] = 10783, [12097] = 12098, [12193] = 12194, [19847] = 19848, [19987] = 19988, [20280] = 20281, [10791] = 10792, [12104] = 12105, [12202] = 12203} -verticalOpenDoors = {1211, 1220, 1224, 1228, 1233, 1238, 1242, 1246, 1251, 1256, 1260, 1540, 3546, 3548, 3550, 3552, 4915, 5083, 5109, 5111, 5113, 5115, 5127, 5129, 5131, 5133, 5142, 5145, 5283, 5285, 5289, 5293, 5516, 5737, 5749, 6194, 6199, 6203, 6207, 6251, 6256, 6260, 6264, 6798, 6802, 6902, 6904, 6906, 6908, 7044, 7046, 7048, 7050, 7055, 8543, 8548, 8552, 8556, 9167, 9172, 9269, 9274, 9274, 9269, 9278, 9282, 10270, 10275, 10279, 10283, 10479, 10481, 10485, 10483, 10786, 12101, 12199, 19851, 19853, 19991, 19993, 20284, 20286, 17238, 13021, 10790, 12103, 12205} -horizontalOpenDoors = {1214, 1222, 1226, 1230, 1236, 1240, 1244, 1248, 1254, 1258, 1262, 1542, 3537, 3539, 3541, 3543, 4918, 5085, 5100, 5102, 5104, 5106, 5118, 5120, 5122, 5124, 5136, 5139, 5280, 5287, 5291, 5295, 5518, 5734, 5746, 6197, 6201, 6205, 6209, 6254, 6258, 6262, 6266, 6796, 6800, 6893, 6895, 6897, 6899, 7035, 7037, 7039, 7041, 7057, 8546, 8550, 8554, 8558, 9170, 9174, 9272, 9276, 9280, 9284, 10273, 10277, 10281, 10285, 10470, 10472, 10476, 10474, 10777, 12094, 12190, 19842, 19844, 19982, 19984, 20275, 20277, 17236, 18209, 13023, 10781, 12096, 12196} -openSpecialDoors = {1224, 1226, 1228, 1230, 1242, 1244, 1246, 1248, 1256, 1258, 1260, 1262, 3541, 3543, 3550, 3552, 5104, 5106, 5113, 5115, 5122, 5124, 5131, 5133, 5289, 5291, 5293, 5295, 6203, 6205, 6207, 6209, 6260, 6262, 6264, 6266, 6897, 6899, 6906, 6908, 7039, 7041, 7048, 7050, 8552, 8554, 8556, 8558, 9176, 9178, 9180, 9182, 9278, 9280, 9282, 9284, 10279, 10281, 10283, 10285, 10474, 10476, 10483, 10485, 10781, 12096, 12196, 19846, 19986, 20279, 10783, 12098, 12194, 19848, 19988, 20281, 10790, 12103, 12205} -questDoors = {1223, 1225, 1241, 1243, 1255, 1257, 3542, 3551, 5105, 5114, 5123, 5132, 5288, 5290, 5745, 5748, 6202, 6204, 6259, 6261, 6898, 6907, 7040, 7049, 8551, 8553, 9175, 9177, 9277, 9279, 10278, 10280, 10475, 10484, 10782, 10791, 12097, 12104, 12193, 12202} -levelDoors = {1227, 1229, 1245, 1247, 1259, 1261, 3540, 3549, 5103, 5112, 5121, 5130, 5292, 5294, 6206, 6208, 6263, 6265, 6896, 6905, 7038, 7047, 8555, 8557, 9179, 9181, 9281, 9283, 10282, 10284, 10473, 10482, 10780, 10789, 10780, 12095, 12102, 12204, 12195} -keys = {2086, 2087, 2088, 2089, 2090, 2091, 2092, 10032} +openDoors = { + 1211, 1214, 1233, 1236, 1251, 1254, 3546, 3537, 4915, 4918, 5100, 5109, 5118, 5127, 5136, 5139, 5142, + 5145, 5280, 5283, 5734, 5737, 6194, 6197, 6251, 6254, 6893, 6902, 7035, 7044, 8543, 8546, 9167, 9170, + 9269, 9272, 10270, 10273, 10470, 10479, 10777, 10786, 12094, 12101, 12190, 12199, 19842, 19851, 19982, + 19991, 20275, 20284, 22816, 22825, 25285, 25292 +} +closedDoors = { + 1210, 1213, 1232, 1235, 1250, 1253, 3536, 3545, 4914, 4917, 5099, 5108, 5117, 5126, 5135, 5138, 5141, + 5144, 5279, 5282, 5733, 5736, 6193, 6196, 6250, 6253, 6892, 6901, 7034, 7043, 8542, 8545, 9166, 9169, + 9268, 9271, 10269, 10272, 10766, 10785, 10469, 10478, 12093, 12100, 12189, 12198, 19841, 19850, 19981, + 19990, 20274, 20283, 22815, 22824, 25284, 25291 +} +lockedDoors = { + 1209, 1212, 1231, 1234, 1249, 1252, 3535, 3544, 4913, 4916, 5098, 5107, 5116, 5125, 5134, 5137, 5140, + 5143, 5278, 5281, 5732, 5735, 6192, 6195, 6249, 6252, 6891, 6900, 7033, 7042, 8541, 8544, 9165, 9168, + 9267, 9270, 10268, 10271, 10468, 10477, 10775, 10784, 12092, 12099, 12188, 12197, 19840, 19849, 19980, + 19989, 20273, 20282, 22814, 22823, 25283, 25290 +} -if nextUseStaminaTime == nil then - nextUseStaminaTime = {} +openExtraDoors = { + 1540, 1542, 6796, 6798, 6800, 6802, 6960, 6962, 7055, 7057, 12695, 12703, 14635, 17236, 17238, 25159, 25161 +} +closedExtraDoors = { + 1539, 1541, 6795, 6797, 6799, 6801, 6959, 6961, 7054, 7056, 12692, 12701, 14633, 17235, 17237, 25158, 25160 +} + +openHouseDoors = { + 1220, 1222, 1238, 1240, 3539, 3548, 5083, 5085, 5102, 5111, 5120, 5129, 5285, 5287, 5516, 5518, 6199, + 6201, 6256, 6258, 6895, 6904, 7037, 7046, 8548, 8550, 9172, 9174, 9274, 9276, 10275, 10277, 10472, 10481, + 13021, 13023, 18209, 19844, 19853, 19984, 19993, 20277, 20286, 22818, 22827 +} +closedHouseDoors = { + 1219, 1221, 1237, 1239, 3538, 3547, 5082, 5084, 5101, 5110, 5119, 5128, 5284, 5286, 5515, 5517, 6198, + 6200, 6255, 6257, 6894, 6903, 7036, 7045, 8547, 8549, 9171, 9173, 9273, 9275, 10274, 10276, 10471, 10480, + 13020, 13022, 18208, 19843, 19852, 19983, 19992, 20276, 20285, 22817, 22826 +} + +--[[ (Not currently used, but probably useful to keep up to date) +openQuestDoors = { + 1224, 1226, 1242, 1244, 1256, 1258, 3543, 3552, 5106, 5115, 5124, 5133, 5289, 5291, 5746, 5749, 6203, + 6205, 6260, 6262, 6899, 6908, 7041, 7050, 8552, 8554, 9176, 9178, 9278, 9280, 10279, 10281, 10476, 10485, + 10783, 10792, 12098, 12105, 12194, 12203, 19848, 19857, 19988, 19997, 20281, 20290, 22822, 22831, 25163, + 25165, 25289, 25296 +} +]]-- +closedQuestDoors = { + 1223, 1225, 1241, 1243, 1255, 1257, 3542, 3551, 5105, 5114, 5123, 5132, 5288, 5290, 5745, 5748, 6202, + 6204, 6259, 6261, 6898, 6907, 7040, 7049, 8551, 8553, 9175, 9177, 9277, 9279, 10278, 10280, 10475, 10484, + 10782, 10791, 12097, 12104, 12193, 12202, 19847, 19856, 19987, 19996, 20280, 20289, 22821, 22830, 25162, + 25164, 25288, 25295 +} + +--[[ (Not currently used, but probably useful to keep up to date) +openLevelDoors = { + 1228, 1230, 1246, 1248, 1260, 1262, 3541, 3550, 5104, 5113, 5122, 5131, 5293, 5295, 6207, 6209, 6264, + 6266, 6897, 6906, 7039, 7048, 8556, 8558, 9180, 9182, 9282, 9284, 10283, 10285, 10474, 10483, 10781, + 10790, 12096, 12103, 12196, 12205, 19846, 19855, 19986, 19995, 20279, 20288, 22820, 22829, 25287, 25294 +} +]]-- +closedLevelDoors = { + 1227, 1229, 1245, 1247, 1259, 1261, 3540, 3549, 5103, 5112, 5121, 5130, 5292, 5294, 6206, 6208, 6263, + 6265, 6896, 6905, 7038, 7047, 8555, 8557, 9179, 9181, 9281, 9283, 10282, 10284, 10473, 10482, 10780, + 10789, 12095, 12102, 12195, 12204, 19845, 19854, 19985, 19994, 20278, 20287, 22819, 22828, 25286, 25293 +} + +function getDistanceBetween(firstPosition, secondPosition) + local xDif = math.abs(firstPosition.x - secondPosition.x) + local yDif = math.abs(firstPosition.y - secondPosition.y) + local posDif = math.max(xDif, yDif) + if firstPosition.z ~= secondPosition.z then + posDif = posDif + 15 + end + return posDif end -if nextUseXpStamina == nil then - nextUseXpStamina = {} +function getFormattedWorldTime() + local worldTime = getWorldTime() + local hours = math.floor(worldTime / 60) + + local minutes = worldTime % 60 + if minutes < 10 then + minutes = '0' .. minutes + end + return hours .. ':' .. minutes end ---Boss entry -if not bosssPlayers then - bosssPlayers = { - addPlayers = function (self, cid) - local player = Player(cid) - if not player then return false end - if not self.players then - self.players = {} - end - self.players[player:getId()] = 1 - end, - removePlayer = function (self, cid) - local player = Player(cid) - if not player then return false end - if not self.players then return false end - self.players[player:getId()] = nil - end, - getPlayersCount = function (self) - if not self.players then return 0 end - local c = 0 - for _ in pairs(self.players) do c = c + 1 end - return c - end - } +function getLootRandom() + return math.random(0, MAX_LOOTCHANCE) / configManager.getNumber(configKeys.RATE_LOOT) end table.contains = function(array, value) @@ -77,3 +127,103 @@ end string.trim = function(str) return str:match'^()%s*$' and '' or str:match'^%s*(.*%S)' end + +if not nextUseStaminaTime then + nextUseStaminaTime = {} +end + +function getPlayerDatabaseInfo(name_or_guid) + local sql_where = "" + + if type(name_or_guid) == 'string' then + sql_where = "WHERE `p`.`name`=" .. db.escapeString(name_or_guid) .. "" + elseif type(name_or_guid) == 'number' then + sql_where = "WHERE `p`.`id`='" .. name_or_guid .. "'" + else + return false + end + + local sql_query = [[ + SELECT + `p`.`id` as `guid`, + `p`.`name`, + CASE WHEN `po`.`player_id` IS NULL + THEN 0 + ELSE 1 + END AS `online`, + `p`.`group_id`, + `p`.`level`, + `p`.`experience`, + `p`.`vocation`, + `p`.`maglevel`, + `p`.`skill_fist`, + `p`.`skill_club`, + `p`.`skill_sword`, + `p`.`skill_axe`, + `p`.`skill_dist`, + `p`.`skill_shielding`, + `p`.`skill_fishing`, + `p`.`town_id`, + `p`.`balance`, + `gm`.`guild_id`, + `gm`.`nick`, + `g`.`name` AS `guild_name`, + CASE WHEN `p`.`id` = `g`.`ownerid` + THEN 1 + ELSE 0 + END AS `is_leader`, + `gr`.`name` AS `rank_name`, + `gr`.`level` AS `rank_level`, + `h`.`id` AS `house_id`, + `h`.`name` AS `house_name`, + `h`.`town_id` AS `house_town` + FROM `players` AS `p` + LEFT JOIN `players_online` AS `po` + ON `p`.`id` = `po`.`player_id` + LEFT JOIN `guild_membership` AS `gm` + ON `p`.`id` = `gm`.`player_id` + LEFT JOIN `guilds` AS `g` + ON `gm`.`guild_id` = `g`.`id` + LEFT JOIN `guild_ranks` AS `gr` + ON `gm`.`rank_id` = `gr`.`id` + LEFT JOIN `houses` AS `h` + ON `p`.`id` = `h`.`owner` + ]] .. sql_where + + local query = db.storeQuery(sql_query) + if not query then + return false + end + + local info = { + ["guid"] = result.getNumber(query, "guid"), + ["name"] = result.getString(query, "name"), + ["online"] = result.getNumber(query, "online"), + ["group_id"] = result.getNumber(query, "group_id"), + ["level"] = result.getNumber(query, "level"), + ["experience"] = result.getNumber(query, "experience"), + ["vocation"] = result.getNumber(query, "vocation"), + ["maglevel"] = result.getNumber(query, "maglevel"), + ["skill_fist"] = result.getNumber(query, "skill_fist"), + ["skill_club"] = result.getNumber(query, "skill_club"), + ["skill_sword"] = result.getNumber(query, "skill_sword"), + ["skill_axe"] = result.getNumber(query, "skill_axe"), + ["skill_dist"] = result.getNumber(query, "skill_dist"), + ["skill_shielding"] = result.getNumber(query, "skill_shielding"), + ["skill_fishing"] = result.getNumber(query, "skill_fishing"), + ["town_id"] = result.getNumber(query, "town_id"), + ["balance"] = result.getNumber(query, "balance"), + ["guild_id"] = result.getNumber(query, "guild_id"), + ["nick"] = result.getString(query, "nick"), + ["guild_name"] = result.getString(query, "guild_name"), + ["is_leader"] = result.getNumber(query, "is_leader"), + ["rank_name"] = result.getString(query, "rank_name"), + ["rank_level"] = result.getNumber(query, "rank_level"), + ["house_id"] = result.getNumber(query, "house_id"), + ["house_name"] = result.getString(query, "house_name"), + ["house_town"] = result.getNumber(query, "house_town") + } + + result.free(query) + return info +end diff --git a/data/globalevents/globalevents.xml b/data/globalevents/globalevents.xml index 2536ba0..83e6df8 100644 --- a/data/globalevents/globalevents.xml +++ b/data/globalevents/globalevents.xml @@ -2,27 +2,30 @@ - + + + + - - - - - - - - + + + + + + + + - - + + - - + + - - + + diff --git a/data/globalevents/scripts/battlefield.lua b/data/globalevents/scripts/custom/battlefield.lua similarity index 100% rename from data/globalevents/scripts/battlefield.lua rename to data/globalevents/scripts/custom/battlefield.lua diff --git a/data/globalevents/scripts/duca.lua b/data/globalevents/scripts/custom/duca.lua similarity index 100% rename from data/globalevents/scripts/duca.lua rename to data/globalevents/scripts/custom/duca.lua diff --git a/data/globalevents/scripts/save.lua b/data/globalevents/scripts/custom/save.lua similarity index 100% rename from data/globalevents/scripts/save.lua rename to data/globalevents/scripts/custom/save.lua diff --git a/data/globalevents/scripts/custom/startup_custom.lua b/data/globalevents/scripts/custom/startup_custom.lua new file mode 100644 index 0000000..154e51d --- /dev/null +++ b/data/globalevents/scripts/custom/startup_custom.lua @@ -0,0 +1,17 @@ +function onStartup() + + -- Check offers in auction system + local days = 7 + local expires = os.time() - (days * 86400) + local resultId = db.storeQuery("SELECT `id` FROM `auction_system` WHERE `date` <= " .. expires) + if resultId ~= false then + local offers_deleted = 0 + repeat + local auctionId = result.getNumber(resultId, "id") + db.asyncQuery("DELETE FROM `auction_system` WHERE `id` = " .. auctionId) + offers_deleted = offers_deleted + 1 + until not result.next(resultId) + result.free(resultId) + print(">> Auction system: ".. offers_deleted .." offers deleted.") + end +end diff --git a/data/globalevents/scripts/zombie.lua b/data/globalevents/scripts/custom/zombie.lua similarity index 100% rename from data/globalevents/scripts/zombie.lua rename to data/globalevents/scripts/custom/zombie.lua diff --git a/data/globalevents/scripts/record.lua b/data/globalevents/scripts/record.lua index 47d6b79..e3ef607 100644 --- a/data/globalevents/scripts/record.lua +++ b/data/globalevents/scripts/record.lua @@ -1,4 +1,4 @@ function onRecord(current, old) - addEvent(broadcastMessage, 150, "New record: " .. current .. " players are logged in.", MESSAGE_STATUS_DEFAULT) + addEvent(Game.broadcastMessage, 150, "New record: " .. current .. " players are logged in.", MESSAGE_STATUS_DEFAULT) return true end diff --git a/data/globalevents/scripts/serversave.lua b/data/globalevents/scripts/serversave.lua index 40e172c..5a2c956 100644 --- a/data/globalevents/scripts/serversave.lua +++ b/data/globalevents/scripts/serversave.lua @@ -1,20 +1,44 @@ -local function serverSave() - Game.setGameState(GAME_STATE_SHUTDOWN) -end +local function ServerSave() + if configManager.getBoolean(configKeys.SERVER_SAVE_SHUTDOWN) then + Game.setGameState(GAME_STATE_SHUTDOWN) + else + local closeAtServerSave = configManager.getBoolean(configKeys.SERVER_SAVE_CLOSE) + if closeAtServerSave then + Game.setGameState(GAME_STATE_CLOSED) + end + + saveServer() -local function secondServerSaveWarning() - Game.broadcastMessage("Server is saving game in one minute. Please logout.", MESSAGE_STATUS_WARNING) - saveServer() - addEvent(serverSave, 60000) + if configManager.getBoolean(configKeys.SERVER_SAVE_CLEAN_MAP) then + cleanMap() + end + + if closeAtServerSave then + Game.setGameState(GAME_STATE_NORMAL) + end + end end -local function firstServerSaveWarning() - Game.broadcastMessage("Server is saving game in 3 minutes. Please logout.", MESSAGE_STATUS_WARNING) - addEvent(secondServerSaveWarning, 120000) +local function ServerSaveWarning(time) + local remaningTime = tonumber(time) - 60000 + + if configManager.getBoolean(configKeys.SERVER_SAVE_NOTIFY_MESSAGE) then + Game.broadcastMessage("Server is saving game in " .. (remaningTime/60000) .." minute(s). Please logout.", MESSAGE_STATUS_WARNING) + end + + if remaningTime > 60000 then + addEvent(ServerSaveWarning, 60000, remaningTime) + else + addEvent(ServerSave, 60000) + end end function onTime(interval) - Game.broadcastMessage("Server is saving game in 5 minutes. Please logout.", MESSAGE_STATUS_WARNING) - addEvent(firstServerSaveWarning, 120000) - return true -end \ No newline at end of file + local remaningTime = configManager.getNumber(configKeys.SERVER_SAVE_NOTIFY_DURATION) * 60000 + if configManager.getBoolean(configKeys.SERVER_SAVE_NOTIFY_MESSAGE) then + Game.broadcastMessage("Server is saving game in " .. (remaningTime/60000) .." minute(s). Please logout.", MESSAGE_STATUS_WARNING) + end + + addEvent(ServerSaveWarning, 60000, remaningTime) + return not configManager.getBoolean(configKeys.SERVER_SAVE_SHUTDOWN) +end diff --git a/data/globalevents/scripts/startup.lua b/data/globalevents/scripts/startup.lua index 5aba30d..b62dd80 100644 --- a/data/globalevents/scripts/startup.lua +++ b/data/globalevents/scripts/startup.lua @@ -41,19 +41,4 @@ function onStartup() local position = town:getTemplePosition() db.query("INSERT INTO `towns` (`id`, `name`, `posx`, `posy`, `posz`) VALUES (" .. town:getId() .. ", " .. db.escapeString(town:getName()) .. ", " .. position.x .. ", " .. position.y .. ", " .. position.z .. ")") end - - -- Check offers in auction system - local days = 7 - local expires = os.time() - (days * 86400) - local resultId = db.storeQuery("SELECT `id` FROM `auction_system` WHERE `date` <= " .. expires) - if resultId ~= false then - local offers_deleted = 0 - repeat - local auctionId = result.getNumber(resultId, "id") - db.asyncQuery("DELETE FROM `auction_system` WHERE `id` = " .. auctionId) - offers_deleted = offers_deleted + 1 - until not result.next(resultId) - result.free(resultId) - print(">> Auction system: ".. offers_deleted .." offers deleted.") - end end diff --git a/data/items/items.otb b/data/items/items.otb index 859c05b..d08e302 100644 Binary files a/data/items/items.otb and b/data/items/items.otb differ diff --git a/data/items/items.xml b/data/items/items.xml index ae8532b..715313e 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -1,5 +1,24 @@ + + + + + + + + + + + + + + + + + + + @@ -202,7 +221,6 @@ - @@ -220,15 +238,19 @@ - + + + + + - + @@ -285,7 +307,7 @@ - + @@ -297,7 +319,7 @@ - + @@ -353,7 +375,7 @@ - + @@ -365,7 +387,7 @@ - + @@ -523,7 +545,7 @@ - + @@ -600,7 +622,7 @@ - + @@ -637,16 +659,15 @@ - + - - + @@ -654,7 +675,6 @@ - @@ -663,21 +683,18 @@ - - - + - @@ -703,7 +720,7 @@ - + @@ -740,7 +757,7 @@ - + @@ -777,7 +794,10 @@ - + + + + @@ -1388,9 +1408,7 @@ - - @@ -1494,7 +1512,7 @@ - + @@ -1676,6 +1694,7 @@ + @@ -1833,7 +1852,7 @@ - + @@ -1902,8 +1921,7 @@ - - + @@ -2185,8 +2203,8 @@ - - + + @@ -2669,7 +2687,7 @@ - + @@ -2922,7 +2940,8 @@ - + + @@ -2936,7 +2955,7 @@ - + @@ -3113,8 +3132,8 @@ - - + + @@ -3176,7 +3195,6 @@ - @@ -4321,7 +4339,7 @@ - + @@ -4417,10 +4435,10 @@ - - - - + + + + @@ -4539,7 +4557,9 @@ - + + + @@ -4647,6 +4667,7 @@ + @@ -4797,7 +4818,7 @@ - + @@ -4805,14 +4826,14 @@ - + - + @@ -4820,14 +4841,14 @@ - + - + @@ -4840,7 +4861,7 @@ - + @@ -4848,7 +4869,7 @@ - + @@ -4884,7 +4905,7 @@ - + @@ -4908,7 +4929,7 @@ - + @@ -4920,14 +4941,14 @@ - + - + @@ -4946,21 +4967,21 @@ - + - + - + @@ -5032,35 +5053,35 @@ - + - + - + - + - + - + - + @@ -5082,7 +5103,7 @@ - + @@ -5090,14 +5111,14 @@ - + - + @@ -5109,7 +5130,7 @@ - + @@ -5123,7 +5144,7 @@ - + @@ -5137,7 +5158,7 @@ - + @@ -5151,7 +5172,7 @@ - + @@ -5159,14 +5180,14 @@ - + - + @@ -5175,7 +5196,7 @@ - + @@ -5183,14 +5204,14 @@ - + - + @@ -5199,7 +5220,7 @@ - + @@ -5207,7 +5228,7 @@ - + @@ -5230,7 +5251,7 @@ - + @@ -5252,22 +5273,22 @@ - + - + - + - + @@ -5289,7 +5310,7 @@ - + @@ -5312,7 +5333,7 @@ - + @@ -5326,7 +5347,7 @@ - + @@ -5345,20 +5366,20 @@ - - + + - + - + @@ -5385,14 +5406,14 @@ - + - + @@ -5405,7 +5426,7 @@ - + @@ -5425,31 +5446,31 @@ - + - + - + - + - + - + - + - + @@ -5475,7 +5496,7 @@ - + @@ -5483,7 +5504,7 @@ - + @@ -5500,7 +5521,7 @@ - + @@ -5508,7 +5529,7 @@ - + @@ -5520,13 +5541,13 @@ - + - + @@ -5564,7 +5585,7 @@ - + @@ -5578,7 +5599,7 @@ - + @@ -5586,7 +5607,7 @@ - + @@ -5604,11 +5625,11 @@ - + - + @@ -5616,7 +5637,7 @@ - + @@ -5650,13 +5671,13 @@ - + - + @@ -5673,14 +5694,14 @@ - + - + @@ -5695,28 +5716,28 @@ - - + + - - + + - + - + @@ -5738,7 +5759,7 @@ - + @@ -5746,50 +5767,50 @@ - + - - + + - - + + - + - - + + - - + + - + - + @@ -5815,7 +5836,7 @@ - + @@ -5823,13 +5844,13 @@ - + - + @@ -5837,65 +5858,65 @@ - + - - + + - - + + - - + + - - + + - - + + - - + + - + - + @@ -5916,20 +5937,20 @@ - + - + - + - + @@ -5937,14 +5958,14 @@ - + - + @@ -5956,12 +5977,12 @@ - + - + @@ -5985,22 +6006,23 @@ + - + - + - + @@ -6024,7 +6046,7 @@ - + @@ -6042,26 +6064,26 @@ - + - + - + - + - + - + - + @@ -6069,7 +6091,7 @@ - + @@ -6081,7 +6103,7 @@ - + @@ -6097,50 +6119,48 @@ - + - - + - + - + - + - - + - + - + - + - + @@ -6150,32 +6170,32 @@ - + - + - + - + - + - + - + - + @@ -6186,14 +6206,14 @@ - + - + @@ -6205,7 +6225,7 @@ - + @@ -6213,7 +6233,7 @@ - + @@ -6249,28 +6269,28 @@ - + - + - + - - + + - + @@ -6298,14 +6318,14 @@ - + - + @@ -6326,20 +6346,20 @@ - + - + - + @@ -6347,13 +6367,13 @@ - + - + @@ -6379,7 +6399,7 @@ - + @@ -6420,7 +6440,7 @@ - + @@ -6430,7 +6450,7 @@ - + @@ -6438,7 +6458,7 @@ - + @@ -6446,7 +6466,7 @@ - + @@ -6479,7 +6499,7 @@ - + @@ -6487,13 +6507,13 @@ - + - + @@ -6554,20 +6574,20 @@ - + - + - + @@ -6584,19 +6604,19 @@ - + - + - + @@ -6606,7 +6626,7 @@ - + @@ -6745,7 +6765,7 @@ - + @@ -6788,7 +6808,7 @@ - + @@ -6822,7 +6842,7 @@ - + @@ -7475,18 +7495,18 @@ - + - + - + @@ -7505,14 +7525,14 @@ - + - + @@ -7523,14 +7543,14 @@ - + - + @@ -7579,7 +7599,7 @@ - + @@ -7596,7 +7616,7 @@ - + @@ -7613,7 +7633,7 @@ - + @@ -7630,7 +7650,7 @@ - + @@ -7647,7 +7667,7 @@ - + @@ -7664,14 +7684,14 @@ - + - + @@ -7699,24 +7719,24 @@ - + - - + + - + - + - + @@ -7733,14 +7753,14 @@ - + - + @@ -7751,15 +7771,15 @@ - - + + - + @@ -7788,7 +7808,7 @@ - + @@ -7805,7 +7825,7 @@ - + @@ -7822,7 +7842,7 @@ - + @@ -7840,7 +7860,7 @@ - + @@ -7863,7 +7883,7 @@ - + @@ -7880,7 +7900,7 @@ - + @@ -7897,14 +7917,14 @@ - + - + @@ -7915,7 +7935,7 @@ - + @@ -7925,15 +7945,12 @@ - - - @@ -8037,6 +8054,8 @@ + + @@ -8108,8 +8127,11 @@ - - + + + + + @@ -8167,7 +8189,7 @@ - + @@ -8179,7 +8201,7 @@ - + @@ -8219,11 +8241,11 @@ - + - + @@ -8315,7 +8337,6 @@ - @@ -8325,7 +8346,7 @@ - + @@ -8359,7 +8380,7 @@ - + @@ -8393,7 +8414,7 @@ - + @@ -8427,7 +8448,7 @@ - + @@ -8461,7 +8482,7 @@ - + @@ -8473,7 +8494,7 @@ - + @@ -8485,7 +8506,7 @@ - + @@ -8497,7 +8518,7 @@ - + @@ -8526,7 +8547,7 @@ - + @@ -8538,7 +8559,7 @@ - + @@ -8758,35 +8779,35 @@ - + - + - + - + - + @@ -8796,16 +8817,15 @@ - - + - + - + @@ -8816,7 +8836,7 @@ - + @@ -8827,7 +8847,7 @@ - + @@ -8838,7 +8858,7 @@ - + @@ -8848,7 +8868,7 @@ - + @@ -8903,7 +8923,7 @@ - + @@ -8932,7 +8952,7 @@ - + @@ -8950,19 +8970,19 @@ - - + + - + - + @@ -8978,14 +8998,14 @@ - + - + @@ -9020,14 +9040,14 @@ - + - + @@ -9071,14 +9091,13 @@ - - + - + @@ -9086,8 +9105,9 @@ - + + @@ -9106,7 +9126,6 @@ - @@ -9134,7 +9153,7 @@ - + @@ -9142,26 +9161,25 @@ - + - - + + - + - + - + - @@ -9177,7 +9195,7 @@ - + @@ -9257,7 +9275,6 @@ - @@ -9426,7 +9443,7 @@ - + @@ -9439,7 +9456,6 @@ - @@ -9467,13 +9483,13 @@ - + - + @@ -9485,7 +9501,7 @@ - + @@ -9582,36 +9598,35 @@ - - + - + - + - + - + @@ -9621,14 +9636,14 @@ - + - + @@ -9642,7 +9657,7 @@ - + @@ -9663,89 +9678,89 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -9755,38 +9770,38 @@ - + - + - + - + - + - + @@ -9800,7 +9815,7 @@ - + @@ -9820,13 +9835,13 @@ - + - + - + @@ -9840,14 +9855,14 @@ - + - + @@ -9861,21 +9876,21 @@ - + - + - + @@ -9888,82 +9903,82 @@ - + - + - + - + - + - + - - + + - + - + - + - + - + - + @@ -9976,22 +9991,21 @@ - - + + - + - - + - + @@ -10014,73 +10028,71 @@ - + - + - + - + - + - - + - + - - + - + - + - + - + - + @@ -10097,11 +10109,11 @@ - - + + - + @@ -10115,14 +10127,14 @@ - + - + @@ -10143,42 +10155,42 @@ - + - + - + - + - + - + @@ -10192,28 +10204,28 @@ - + - - + + - + - + - + @@ -10227,7 +10239,7 @@ - + @@ -10241,77 +10253,77 @@ - + - + - + - + - + - + - + - + - + - + - + @@ -10321,8 +10333,7 @@ - - + @@ -10332,39 +10343,40 @@ - + - + - + - + - + + - + @@ -10378,33 +10390,34 @@ - + - + + - + - + - + - + - + @@ -10586,7 +10599,7 @@ - + @@ -10598,7 +10611,7 @@ - + @@ -10661,7 +10674,7 @@ - + @@ -10673,7 +10686,7 @@ - + @@ -10741,16 +10754,15 @@ - + - + - @@ -10774,20 +10786,20 @@ - + - + - + @@ -10798,19 +10810,19 @@ - + - + - + @@ -10821,32 +10833,32 @@ - + - + - + - + - + @@ -10857,88 +10869,79 @@ - + - + - + - - + - - + - - + - - + + - + - - + + - + - + - - - + - - - + - - - + - + @@ -10949,7 +10952,7 @@ - + @@ -10979,19 +10982,19 @@ - + - + - + @@ -11002,21 +11005,21 @@ - + - + - + @@ -11027,21 +11030,21 @@ - + - + - + @@ -11053,21 +11056,21 @@ - + - + - + @@ -11078,18 +11081,18 @@ - - + + @@ -11120,14 +11123,14 @@ - + - + @@ -11189,7 +11192,7 @@ - + @@ -11349,31 +11352,31 @@ - + - - + + - + - - + + - + - + - + @@ -11404,9 +11407,8 @@ - - - + + @@ -11414,7 +11416,7 @@ - + @@ -11483,7 +11485,6 @@ - @@ -11492,9 +11493,8 @@ - - + @@ -11519,7 +11519,7 @@ - + @@ -11699,12 +11699,11 @@ - - + @@ -11738,7 +11737,7 @@ - + @@ -11792,7 +11791,6 @@ - @@ -11828,7 +11826,7 @@ - + @@ -11862,7 +11860,7 @@ - + @@ -11920,19 +11918,19 @@ - + - + - + @@ -11994,11 +11992,12 @@ - + + @@ -12078,29 +12077,29 @@ - + - - + + - + - - + + - + - + @@ -12116,19 +12115,19 @@ - + - + - + @@ -12142,7 +12141,6 @@ - @@ -12157,7 +12155,6 @@ - @@ -12192,21 +12189,21 @@ - + - + - + @@ -12217,61 +12214,61 @@ - + - + - + - + - + - + - + - + - + @@ -12282,21 +12279,21 @@ - + - + - + @@ -12342,19 +12339,19 @@ - + - + - + @@ -12530,11 +12527,11 @@ - - + + @@ -12699,7 +12696,7 @@ - + @@ -12882,11 +12879,11 @@ - - + + - + @@ -12897,8 +12894,8 @@ - - + + @@ -13163,7 +13160,10 @@ - + + + + @@ -13188,6 +13188,8 @@ + + @@ -13244,35 +13246,35 @@ - + - + - + - + - + @@ -13330,7 +13332,6 @@ - @@ -13351,7 +13352,7 @@ - + @@ -13364,15 +13365,21 @@ + + + + - + + + @@ -13382,37 +13389,93 @@ - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + + + + + + + + + - + + + + + + + + + - + + + + + + + + + - + + + @@ -13463,7 +13526,9 @@ - + + + @@ -13492,7 +13557,12 @@ - + + + + + + @@ -13529,21 +13599,21 @@ - + - + - + @@ -13706,7 +13776,6 @@ - @@ -13796,7 +13865,6 @@ - @@ -14197,17 +14265,17 @@ - + - + - + @@ -14733,7 +14801,7 @@ - + @@ -14741,7 +14809,7 @@ - + @@ -14758,7 +14826,7 @@ - + @@ -14766,7 +14834,7 @@ - + @@ -14801,12 +14869,11 @@ - - + @@ -14854,27 +14921,26 @@ - - + + - - + + - - + + - @@ -14885,7 +14951,6 @@ - @@ -14896,16 +14961,13 @@ - - - + - @@ -14925,7 +14987,6 @@ - @@ -14942,17 +15003,14 @@ - - - @@ -14960,12 +15018,10 @@ - - @@ -14983,37 +15039,34 @@ - - - + - - + + - - + + - @@ -15037,7 +15090,6 @@ - @@ -15064,32 +15116,29 @@ - - + - - - + - + - + - + @@ -15130,8 +15179,8 @@ - - + + @@ -15153,7 +15202,7 @@ - + @@ -15161,7 +15210,7 @@ - + @@ -15176,7 +15225,7 @@ - + @@ -15263,14 +15312,24 @@ - + + + + + + - + + + + + + @@ -15341,7 +15400,7 @@ - + @@ -15533,13 +15592,7 @@ - - - - - - - + @@ -15607,6 +15660,7 @@ + @@ -15654,13 +15708,13 @@ - + - + @@ -15672,7 +15726,7 @@ - + @@ -15847,6 +15901,7 @@ + @@ -15972,7 +16027,6 @@ - @@ -16177,30 +16231,30 @@ - + - + - + - + - + - + - + @@ -16231,21 +16285,21 @@ - + - + - + @@ -16293,21 +16347,21 @@ - + - + - + @@ -16320,21 +16374,21 @@ - + - + - + @@ -16351,27 +16405,27 @@ - + - + - + - + @@ -16453,6 +16507,8 @@ + + @@ -16492,6 +16548,9 @@ + + + @@ -16531,33 +16590,38 @@ - + - - + - - - + - + - - + + + + + + + + + + - + @@ -16584,13 +16648,13 @@ - + - + @@ -16613,7 +16677,7 @@ - + @@ -16642,21 +16706,31 @@ - + + + + + + - + + + + + + - + @@ -16664,7 +16738,7 @@ - + @@ -16719,21 +16793,31 @@ - + + + + + + - + + + + + + - + @@ -16741,7 +16825,7 @@ - + @@ -16818,6 +16902,12 @@ + + + + + + @@ -16876,7 +16966,7 @@ - + @@ -16985,10 +17075,10 @@ - - + + - + @@ -17026,17 +17116,9 @@ - + - - - - - - - - - + @@ -17106,34 +17188,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + - + - - + - + - + - + - + + + + + @@ -17144,7 +17260,7 @@ - + @@ -17156,44 +17272,44 @@ - + - + - + - + - + - + - + @@ -17208,89 +17324,87 @@ - - + + - - + - - + + - - + - - + + - + - - + + - + - + - + - + - + - + - + - + - + - + - + @@ -17302,7 +17416,7 @@ - + @@ -17341,74 +17455,74 @@ - - + + - - + + - - + + - - + + - + - + - + - + - + - + - + - + @@ -17454,30 +17568,29 @@ - - - + + - + - + - + - + @@ -17527,8 +17640,8 @@ - - + + @@ -17543,37 +17656,37 @@ - + - - + + - - + + - + - - + + - - + + @@ -17610,23 +17723,15 @@ - - - - - - - - @@ -17647,7 +17752,7 @@ - + @@ -17730,50 +17835,48 @@ - + - - + - + - + - + - + - - + - + - + - + - + @@ -17791,10 +17894,7 @@ - - - - + @@ -17891,32 +17991,101 @@ - + + + + + + + + + + + + + - - - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -17925,6 +18094,7 @@ + @@ -17961,34 +18131,35 @@ + - + - + - + - + - + - + - + @@ -17998,7 +18169,7 @@ - + @@ -18021,7 +18192,7 @@ - + @@ -18031,7 +18202,7 @@ - + @@ -18096,7 +18267,7 @@ - + @@ -18176,6 +18347,7 @@ + @@ -18183,9 +18355,13 @@ + + + + @@ -18255,7 +18431,7 @@ - + @@ -18267,7 +18443,7 @@ - + @@ -18452,26 +18628,26 @@ - + - + - + - + @@ -18514,42 +18690,42 @@ - + - + - + - + - + - + @@ -18606,7 +18782,7 @@ - + @@ -18640,7 +18816,7 @@ - + @@ -18685,6 +18861,7 @@ + @@ -18704,9 +18881,9 @@ - + - + @@ -18726,7 +18903,7 @@ - + @@ -18736,7 +18913,7 @@ - + @@ -18746,7 +18923,7 @@ - + @@ -18780,14 +18957,14 @@ - + - + @@ -18854,7 +19031,7 @@ - + @@ -18976,7 +19153,7 @@ - + @@ -18988,7 +19165,7 @@ - + @@ -19000,6 +19177,7 @@ + @@ -19017,27 +19195,33 @@ - + - + + + + + + + - + - + - + @@ -19050,7 +19234,7 @@ - + @@ -19078,7 +19262,7 @@ - + @@ -19185,7 +19369,7 @@ - + @@ -19195,7 +19379,6 @@ - @@ -19205,7 +19388,7 @@ - + @@ -19215,10 +19398,9 @@ - - + @@ -19233,9 +19415,9 @@ - + - + @@ -19265,14 +19447,14 @@ - + - + @@ -19324,9 +19506,8 @@ - - + @@ -19370,19 +19551,18 @@ - - - + + - + - + @@ -19402,7 +19582,7 @@ - + @@ -19412,7 +19592,7 @@ - + @@ -19484,7 +19664,7 @@ - + @@ -19496,7 +19676,7 @@ - + @@ -19514,10 +19694,10 @@ - + - + @@ -19559,7 +19739,7 @@ - + @@ -19623,7 +19803,7 @@ - + @@ -19636,7 +19816,7 @@ - + @@ -19655,7 +19835,7 @@ - + @@ -19681,7 +19861,7 @@ - + @@ -19770,14 +19950,14 @@ - + - + @@ -19794,7 +19974,7 @@ - + @@ -19802,13 +19982,13 @@ - + - + @@ -19903,14 +20083,14 @@ - + - + @@ -19926,6 +20106,10 @@ + + + + @@ -19948,10 +20132,10 @@ - + - + @@ -19961,16 +20145,16 @@ - + - + - + @@ -19979,7 +20163,7 @@ - + @@ -20012,14 +20196,14 @@ - + - + @@ -20081,29 +20265,27 @@ - + - - + - + - - + - + - + - + @@ -20120,11 +20302,11 @@ - + - + @@ -20175,11 +20357,11 @@ + - - + + - @@ -20197,9 +20379,15 @@ - - + + + + + + + + @@ -20207,7 +20395,7 @@ - + @@ -20280,26 +20468,13 @@ - - - - - - - - - - - - - - + @@ -20322,7 +20497,8 @@ - + + @@ -20333,15 +20509,16 @@ - - - - + + + - + + + @@ -20360,7 +20537,8 @@ - + + @@ -20378,16 +20556,14 @@ - - + + - - @@ -20397,9 +20573,11 @@ + + - + @@ -20426,7 +20604,7 @@ - + @@ -20453,20 +20631,27 @@ + - + + - + + + + + - + + @@ -20492,9 +20677,10 @@ - + + @@ -20522,16 +20708,13 @@ - - + - - - - + + @@ -20561,7 +20744,7 @@ - + @@ -20571,14 +20754,15 @@ - + + - + @@ -20596,7 +20780,6 @@ - @@ -20617,14 +20800,15 @@ - + + + - - + @@ -20672,7 +20856,7 @@ - + @@ -20782,7 +20966,7 @@ - + @@ -20817,7 +21001,7 @@ - + @@ -20846,6 +21030,14 @@ + + + + + + + + @@ -20875,8 +21067,7 @@ - - + @@ -20931,25 +21122,18 @@ - + - - - - - - - - - - - + + + - - - + + + + @@ -21152,14 +21336,14 @@ - + - + @@ -21200,14 +21384,14 @@ - + - + @@ -21241,13 +21425,13 @@ - + - + @@ -21350,7 +21534,11 @@ - + + + + + @@ -21376,23 +21564,4 @@ - - - - - - - - - - - - - - - - - - - diff --git a/data/lib/compat/compat.lua b/data/lib/compat/compat.lua new file mode 100644 index 0000000..9264d66 --- /dev/null +++ b/data/lib/compat/compat.lua @@ -0,0 +1,1491 @@ +TRUE = true +FALSE = false + +result.getDataInt = result.getNumber +result.getDataLong = result.getNumber +result.getDataString = result.getString +result.getDataStream = result.getStream + +LUA_ERROR = false +LUA_NO_ERROR = true + +STACKPOS_GROUND = 0 +STACKPOS_FIRST_ITEM_ABOVE_GROUNDTILE = 1 +STACKPOS_SECOND_ITEM_ABOVE_GROUNDTILE = 2 +STACKPOS_THIRD_ITEM_ABOVE_GROUNDTILE = 3 +STACKPOS_FOURTH_ITEM_ABOVE_GROUNDTILE = 4 +STACKPOS_FIFTH_ITEM_ABOVE_GROUNDTILE = 5 +STACKPOS_TOP_CREATURE = 253 +STACKPOS_TOP_FIELD = 254 +STACKPOS_TOP_MOVEABLE_ITEM_OR_CREATURE = 255 + +THING_TYPE_PLAYER = CREATURETYPE_PLAYER + 1 +THING_TYPE_MONSTER = CREATURETYPE_MONSTER + 1 +THING_TYPE_NPC = CREATURETYPE_NPC + 1 + +COMBAT_POISONDAMAGE = COMBAT_EARTHDAMAGE +CONDITION_EXHAUST = CONDITION_EXHAUST_WEAPON +TALKTYPE_ORANGE_1 = TALKTYPE_MONSTER_SAY +TALKTYPE_ORANGE_2 = TALKTYPE_MONSTER_YELL + +NORTH = DIRECTION_NORTH +EAST = DIRECTION_EAST +SOUTH = DIRECTION_SOUTH +WEST = DIRECTION_WEST +SOUTHWEST = DIRECTION_SOUTHWEST +SOUTHEAST = DIRECTION_SOUTHEAST +NORTHWEST = DIRECTION_NORTHWEST +NORTHEAST = DIRECTION_NORTHEAST + +do + local function CreatureIndex(self, key) + local methods = getmetatable(self) + if key == "uid" then + return methods.getId(self) + elseif key == "type" then + local creatureType = 0 + if methods.isPlayer(self) then + creatureType = THING_TYPE_PLAYER + elseif methods.isMonster(self) then + creatureType = THING_TYPE_MONSTER + elseif methods.isNpc(self) then + creatureType = THING_TYPE_NPC + end + return creatureType + elseif key == "itemid" then + return 1 + elseif key == "actionid" then + return 0 + end + return methods[key] + end + rawgetmetatable("Player").__index = CreatureIndex + rawgetmetatable("Monster").__index = CreatureIndex + rawgetmetatable("Npc").__index = CreatureIndex +end + +do + local function ItemIndex(self, key) + local methods = getmetatable(self) + if key == "itemid" then + return methods.getId(self) + elseif key == "actionid" then + return methods.getActionId(self) + elseif key == "uid" then + return methods.getUniqueId(self) + elseif key == "type" then + return methods.getSubType(self) + end + return methods[key] + end + rawgetmetatable("Item").__index = ItemIndex + rawgetmetatable("Container").__index = ItemIndex + rawgetmetatable("Teleport").__index = ItemIndex +end + +do + local function ActionNewIndex(self, key, value) + if key == "onUse" then + self:onUse(value) + return + end + rawset(self, key, value) + end + rawgetmetatable("Action").__newindex = ActionNewIndex +end + +do + local function TalkActionNewIndex(self, key, value) + if key == "onSay" then + self:onSay(value) + return + end + rawset(self, key, value) + end + rawgetmetatable("TalkAction").__newindex = TalkActionNewIndex +end + +do + local function CreatureEventNewIndex(self, key, value) + if key == "onLogin" then + self:type("login") + self:onLogin(value) + return + elseif key == "onLogout" then + self:type("logout") + self:onLogout(value) + return + elseif key == "onThink" then + self:type("think") + self:onThink(value) + return + elseif key == "onPrepareDeath" then + self:type("preparedeath") + self:onPrepareDeath(value) + return + elseif key == "onDeath" then + self:type("death") + self:onDeath(value) + return + elseif key == "onKill" then + self:type("kill") + self:onKill(value) + return + elseif key == "onAdvance" then + self:type("advance") + self:onAdvance(value) + return + elseif key == "onModalWindow" then + self:type("modalwindow") + self:onModalWindow(value) + return + elseif key == "onTextEdit" then + self:type("textedit") + self:onTextEdit(value) + return + elseif key == "onHealthChange" then + self:type("healthchange") + self:onHealthChange(value) + return + elseif key == "onManaChange" then + self:type("manachange") + self:onManaChange(value) + return + elseif key == "onExtendedOpcode" then + self:type("extendedopcode") + self:onExtendedOpcode(value) + return + end + rawset(self, key, value) + end + rawgetmetatable("CreatureEvent").__newindex = CreatureEventNewIndex +end + +do + local function MoveEventNewIndex(self, key, value) + if key == "onEquip" then + self:type("equip") + self:onEquip(value) + return + elseif key == "onDeEquip" then + self:type("deequip") + self:onDeEquip(value) + return + elseif key == "onAddItem" then + self:type("additem") + self:onAddItem(value) + return + elseif key == "onRemoveItem" then + self:type("removeitem") + self:onRemoveItem(value) + return + elseif key == "onStepIn" then + self:type("stepin") + self:onStepIn(value) + return + elseif key == "onStepOut" then + self:type("stepout") + self:onStepOut(value) + return + end + rawset(self, key, value) + end + rawgetmetatable("MoveEvent").__newindex = MoveEventNewIndex +end + +do + local function GlobalEventNewIndex(self, key, value) + if key == "onThink" then + self:onThink(value) + return + elseif key == "onTime" then + self:onTime(value) + return + elseif key == "onStartup" then + self:type("startup") + self:onStartup(value) + return + elseif key == "onShutdown" then + self:type("shutdown") + self:onShutdown(value) + return + elseif key == "onRecord" then + self:type("record") + self:onRecord(value) + return + end + rawset(self, key, value) + end + rawgetmetatable("GlobalEvent").__newindex = GlobalEventNewIndex +end + +do + local function WeaponNewIndex(self, key, value) + if key == "onUseWeapon" then + self:onUseWeapon(value) + return + end + rawset(self, key, value) + end + rawgetmetatable("Weapon").__newindex = WeaponNewIndex +end + +do + local function SpellNewIndex(self, key, value) + if key == "onCastSpell" then + self:onCastSpell(value) + return + end + rawset(self, key, value) + end + rawgetmetatable("Spell").__newindex = SpellNewIndex +end + +do + local function MonsterTypeNewIndex(self, key, value) + if key == "onThink" then + self:eventType(MONSTERS_EVENT_THINK) + self:onThink(value) + return + elseif key == "onAppear" then + self:eventType(MONSTERS_EVENT_APPEAR) + self:onAppear(value) + return + elseif key == "onDisappear" then + self:eventType(MONSTERS_EVENT_DISAPPEAR) + self:onDisappear(value) + return + elseif key == "onMove" then + self:eventType(MONSTERS_EVENT_MOVE) + self:onMove(value) + return + elseif key == "onSay" then + self:eventType(MONSTERS_EVENT_SAY) + self:onSay(value) + return + end + rawset(self, key, value) + end + rawgetmetatable("MonsterType").__newindex = MonsterTypeNewIndex +end + +function pushThing(thing) + local t = {uid = 0, itemid = 0, type = 0, actionid = 0} + if thing then + if thing:isItem() then + t.uid = thing:getUniqueId() + t.itemid = thing:getId() + if ItemType(t.itemid):hasSubType() then + t.type = thing:getSubType() + end + t.actionid = thing:getActionId() + elseif thing:isCreature() then + t.uid = thing:getId() + t.itemid = 1 + if thing:isPlayer() then + t.type = THING_TYPE_PLAYER + elseif thing:isMonster() then + t.type = THING_TYPE_MONSTER + else + t.type = THING_TYPE_NPC + end + end + end + return t +end + +createCombatObject = Combat +addCombatCondition = Combat.addCondition +setCombatArea = Combat.setArea +setCombatCallback = Combat.setCallback +setCombatFormula = Combat.setFormula +setCombatParam = Combat.setParameter + +Combat.setCondition = function(...) + print("[Warning - " .. debug.getinfo(2).source:match("@?(.*)") .. "] Function Combat.setCondition was renamed to Combat.addCondition and will be removed in the future") + Combat.addCondition(...) +end + +setCombatCondition = function(...) + print("[Warning - " .. debug.getinfo(2).source:match("@?(.*)") .. "] Function setCombatCondition was renamed to addCombatCondition and will be removed in the future") + Combat.addCondition(...) +end + +function doTargetCombatHealth(...) return doTargetCombat(...) end +function doAreaCombatHealth(...) return doAreaCombat(...) end +doCombatAreaHealth = doAreaCombatHealth +function doTargetCombatMana(cid, target, min, max, effect) return doTargetCombat(cid, target, COMBAT_MANADRAIN, min, max, effect) end +doCombatAreaMana = doTargetCombatMana +function doAreaCombatMana(cid, pos, area, min, max, effect) return doAreaCombat(cid, COMBAT_MANADRAIN, pos, area, min, max, effect) end + +createConditionObject = Condition +setConditionParam = Condition.setParameter +setConditionFormula = Condition.setFormula +addDamageCondition = Condition.addDamage +addOutfitCondition = Condition.setOutfit + +function doCombat(cid, combat, var) return combat:execute(cid, var) end + +function isCreature(cid) return Creature(cid) end +function isPlayer(cid) return Player(cid) end +function isMonster(cid) return Monster(cid) end +function isSummon(cid) local c = Creature(cid) return c and c:getMaster() end +function isNpc(cid) return Npc(cid) end +function isItem(uid) return Item(uid) end +function isContainer(uid) return Container(uid) end + +function getCreatureName(cid) local c = Creature(cid) return c and c:getName() or false end +function getCreatureStorage(uid, key) local c = Creature(uid) return c and c:getStorageValue(key) or false end +function getCreatureHealth(cid) local c = Creature(cid) return c and c:getHealth() or false end +function getCreatureMaxHealth(cid) local c = Creature(cid) return c and c:getMaxHealth() or false end +function getCreatureMana(cid) local c = Creature(cid) return c and c:getMana() or false end +function getCreatureMaxMana(cid) local c = Creature(cid) return c and c:getMaxMana() or false end +function getCreaturePosition(cid) local c = Creature(cid) return c and c:getPosition() or false end +function getCreatureOutfit(cid) local c = Creature(cid) return c and c:getOutfit() or false end +function getCreatureSpeed(cid) local c = Creature(cid) return c and c:getSpeed() or false end +function getCreatureBaseSpeed(cid) local c = Creature(cid) return c and c:getBaseSpeed() or false end +function getCreatureLookDirection(cid) local c = Creature(cid) return c and c:getDirection() or false end +function getCreatureHideHealth(cid) local c = Creature(cid) return c and c:isHealthHidden() or false end +function getCreatureSkullType(cid) local c = Creature(cid) return c and c:getSkull() or false end +function getCreatureNoMove(cid) local c = Creature(cid) return c and c:isMovementBlocked() or false end + +function getCreatureTarget(cid) + local c = Creature(cid) + if c then + local target = c:getTarget() + return target and target:getId() or 0 + end + return false +end + +function getCreatureMaster(cid) + local c = Creature(cid) + if c then + local master = c:getMaster() + return master and master:getId() or c:getId() + end + return false +end + +function getCreatureSummons(cid) + local c = Creature(cid) + if c == nil then + return false + end + + local result = {} + for _, summon in ipairs(c:getSummons()) do + result[#result + 1] = summon:getId() + end + return result +end + +getCreaturePos = getCreaturePosition + +function doCreatureAddHealth(cid, health) local c = Creature(cid) return c and c:addHealth(health) or false end +function doCreatureAddMana(cid, mana) local c = Creature(cid) return c and c:addMana(mana) or false end +function doRemoveCreature(cid) local c = Creature(cid) return c and c:remove() or false end +function doCreatureSetStorage(uid, key, value) local c = Creature(uid) return c and c:setStorageValue(key, value) or false end +function doCreatureSetLookDir(cid, direction) local c = Creature(cid) return c and c:setDirection(direction) or false end +function doCreatureSetSkullType(cid, skull) local c = Creature(cid) return c and c:setSkull(skull) or false end +function setCreatureMaxHealth(cid, health) local c = Creature(cid) return c and c:setMaxHealth(health) or false end +function setCreatureMaxMana(cid, mana) local c = Creature(cid) return c and c:setMaxMana(mana) or false end +function doCreatureSetHideHealth(cid, hide) local c = Creature(cid) return c and c:setHiddenHealth(hide) or false end +function doCreatureSetNoMove(cid, block) local c = Creature(cid) return c and c:setMovementBlocked(block) or false end +function doCreatureSay(cid, text, type, ...) local c = Creature(cid) return c and c:say(text, type, ...) or false end +function doCreatureChangeOutfit(cid, outfit) local c = Creature(cid) return c and c:setOutfit(outfit) or false end +function doSetCreatureDropLoot(cid, doDrop) local c = Creature(cid) return c and c:setDropLoot(doDrop) or false end +doCreatureSetDropLoot = doSetCreatureDropLoot +function doChangeSpeed(cid, delta) local c = Creature(cid) return c and c:changeSpeed(delta) or false end +function doAddCondition(cid, conditionId) local c = Creature(cid) return c and c:addCondition(conditionId) or false end +function doRemoveCondition(cid, conditionType, subId) local c = Creature(cid) return c and (c:removeCondition(conditionType, CONDITIONID_COMBAT, subId) or c:removeCondition(conditionType, CONDITIONID_DEFAULT, subId) or true) end +function getCreatureCondition(cid, type, subId) local c = Creature(cid) return c and c:hasCondition(type, subId) or false end + +doCreatureSetLookDirection = doCreatureSetLookDir +doSetCreatureDirection = doCreatureSetLookDir + +function registerCreatureEvent(cid, name) local c = Creature(cid) return c and c:registerEvent(name) or false end +function unregisterCreatureEvent(cid, name) local c = Creature(cid) return c and c:unregisterEvent(name) or false end + +function getPlayerByName(name) local p = Player(name) return p and p:getId() or false end +function getIPByPlayerName(name) local p = Player(name) return p and p:getIp() or false end +function getPlayerGUID(cid) local p = Player(cid) return p and p:getGuid() or false end +function getPlayerNameDescription(cid, distance) local p = Player(cid) return p and p:getDescription(distance) or false end +function getPlayerSpecialDescription() debugPrint("Deprecated function, use Player:onLook event instead.") return true end +function getPlayerAccountId(cid) local p = Player(cid) return p and p:getAccountId() or false end +getPlayerAccount = getPlayerAccountId +function getPlayerIp(cid) local p = Player(cid) return p and p:getIp() or false end +function getPlayerAccountType(cid) local p = Player(cid) return p and p:getAccountType() or false end +function getPlayerLastLoginSaved(cid) local p = Player(cid) return p and p:getLastLoginSaved() or false end +getPlayerLastLogin = getPlayerLastLoginSaved +function getPlayerName(cid) local p = Player(cid) return p and p:getName() or false end +getPlayerNameDescription = getPlayerName +function getPlayerFreeCap(cid) local p = Player(cid) return p and (p:getFreeCapacity() / 100) or false end +function getPlayerPosition(cid) local p = Player(cid) return p and p:getPosition() or false end +function getPlayerMagLevel(cid) local p = Player(cid) return p and p:getMagicLevel() or false end +function getPlayerSpentMana(cid) local p = Player(cid) return p and p:getManaSpent() or false end +function getPlayerRequiredMana(cid, magicLevel) local p = Player(cid) return p and p:getVocation():getRequiredManaSpent(magicLevel) or false end +function getPlayerRequiredSkillTries(cid, skillId) local p = Player(cid) return p and p:getVocation():getRequiredSkillTries(skillId) or false end +function getPlayerAccess(cid) + local player = Player(cid) + if player == nil then + return false + end + return player:getGroup():getAccess() and 1 or 0 +end +function getPlayerSkill(cid, skillId) local p = Player(cid) return p and p:getSkillLevel(skillId) or false end +getPlayerSkillLevel = getPlayerSkill +function getPlayerSkillTries(cid, skillId) local p = Player(cid) return p and p:getSkillTries(skillId) or false end +function getPlayerMana(cid) local p = Player(cid) return p and p:getMana() or false end +function getPlayerMaxMana(cid) local p = Player(cid) return p and p:getMaxMana() or false end +function getPlayerLevel(cid) local p = Player(cid) return p and p:getLevel() or false end +function getPlayerExperience(cid) local p = Player(cid) return p and p:getExperience() or false end +function getPlayerTown(cid) local p = Player(cid) return p and p:getTown():getId() or false end +function getPlayerVocation(cid) local p = Player(cid) return p and p:getVocation():getId() or false end +function getPlayerSoul(cid) local p = Player(cid) return p and p:getSoul() or false end +function getPlayerSex(cid) local p = Player(cid) return p and p:getSex() or false end +function getPlayerStorageValue(cid, key) local p = Player(cid) return p and p:getStorageValue(key) or false end +function getPlayerBalance(cid) local p = Player(cid) return p and p:getBankBalance() or false end +function getPlayerMoney(cid) local p = Player(cid) return p and p:getMoney() or false end +function getPlayerGroupId(cid) local p = Player(cid) return p and p:getGroup():getId() or false end +function getPlayerLookDir(cid) local p = Player(cid) return p and p:getDirection() or false end +function getPlayerLight(cid) local p = Player(cid) return p and p:getLight() or false end +function getPlayerDepotItems(cid, depotId) local p = Player(cid) return p and p:getDepotItems(depotId) or false end +function getPlayerStamina(cid) local p = Player(cid) return p and p:getStamina() or false end +function getPlayerSkullType(cid) local p = Player(cid) return p and p:getSkull() or false end +function getPlayerLossPercent(cid) local p = Player(cid) return p and p:getDeathPenalty() or false end +function getPlayerPremiumDays(cid) local p = Player(cid) return p and p:getPremiumDays() or false end +function getPlayerBlessing(cid, blessing) local p = Player(cid) return p and p:hasBlessing(blessing) or false end +function getPlayerFlagValue(cid, flag) local p = Player(cid) return p ~= nil and p:hasFlag(flag) or false end +function getPlayerCustomFlagValue() debugPrint("Deprecated function, use player:hasFlag(flag) instead.") return true end + +function getPlayerParty(cid) + local player = Player(cid) + if player == nil then + return false + end + + local party = player:getParty() + if party == nil then + return nil + end + return party:getLeader():getId() +end +function getPlayerGuildId(cid) + local player = Player(cid) + if player == nil then + return false + end + + local guild = player:getGuild() + if guild == nil then + return false + end + return guild:getId() +end +function getPlayerGuildLevel(cid) local p = Player(cid) return p and p:getGuildLevel() or false end +function getPlayerGuildName(cid) + local player = Player(cid) + if player == nil then + return false + end + + local guild = player:getGuild() + if guild == nil then + return false + end + return guild:getName() +end +function getPlayerGuildRank(cid) + local player = Player(cid) + if player == nil then + return false + end + + local guild = player:getGuild() + if guild == nil then + return false + end + + local rank = guild:getRankByLevel(player:getGuildLevel()) + return rank and rank.name or false +end +function getPlayerGuildRankId(cid) local p = Player(cid) return p and p:getGuildLevel() or false end +function getPlayerGuildNick(cid) local p = Player(cid) return p and p:getGuildNick() or false end +function getPlayerMasterPos(cid) local p = Player(cid) return p and p:getTown():getTemplePosition() or false end +function getPlayerItemCount(cid, itemId, ...) local p = Player(cid) return p and p:getItemCount(itemId, ...) or false end +function getPlayerWeapon(cid) local p = Player(cid) return p and p:getWeaponType() or false end +function getPlayerSlotItem(cid, slot) + local player = Player(cid) + if player == nil then + return pushThing(nil) + end + return pushThing(player:getSlotItem(slot)) +end +function getPlayerItemById(cid, deepSearch, itemId, ...) + local player = Player(cid) + if player == nil then + return pushThing(nil) + end + return pushThing(player:getItemById(itemId, deepSearch, ...)) +end +function getPlayerFood(cid) + local player = Player(cid) + if player == nil then + return false + end + local c = player:getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT) return c and math.floor(c:getTicks() / 1000) or 0 +end +function canPlayerLearnInstantSpell(cid, name) local p = Player(cid) return p and p:canLearnSpell(name) or false end +function getPlayerLearnedInstantSpell(cid, name) local p = Player(cid) return p and p:hasLearnedSpell(name) or false end +function isPlayerGhost(cid) local p = Player(cid) return p and p:isInGhostMode() or false end +function isPlayerPzLocked(cid) local p = Player(cid) return p and p:isPzLocked() or false end +function isPremium(cid) local p = Player(cid) return p and p:isPremium() or false end +function getPlayersByIPAddress(ip, mask) + if mask == nil then mask = 0xFFFFFFFF end + local masked = bit.band(ip, mask) + local result = {} + for _, player in ipairs(Game.getPlayers()) do + if bit.band(player:getIp(), mask) == masked then + result[#result + 1] = player:getId() + end + end + return result +end +getPlayersByIp = getPlayersByIPAddress +function getOnlinePlayers() + local result = {} + for _, player in ipairs(Game.getPlayers()) do + result[#result + 1] = player:getName() + end + return result +end +getPlayersOnline = getOnlinePlayers +function getPlayersByAccountNumber(accountNumber) + local result = {} + for _, player in ipairs(Game.getPlayers()) do + if player:getAccountId() == accountNumber then + result[#result + 1] = player:getId() + end + end + return result +end +function getPlayerGUIDByName(name) + local player = Player(name) + if player then + return player:getGuid() + end + + local resultId = db.storeQuery("SELECT `id` FROM `players` WHERE `name` = " .. db.escapeString(name)) + if resultId ~= false then + local guid = result.getNumber(resultId, "id") + result.free(resultId) + return guid + end + return 0 +end +function getAccountNumberByPlayerName(name) + local player = Player(name) + if player then + return player:getAccountId() + end + + local resultId = db.storeQuery("SELECT `account_id` FROM `players` WHERE `name` = " .. db.escapeString(name)) + if resultId ~= false then + local accountId = result.getNumber(resultId, "account_id") + result.free(resultId) + return accountId + end + return 0 +end + +getPlayerAccountBalance = getPlayerBalance +getIpByName = getIPByPlayerName + +function setPlayerStorageValue(cid, key, value) local p = Player(cid) return p and p:setStorageValue(key, value) or false end +function doPlayerSetNameDescription() debugPrint("Deprecated function, use Player:onLook event instead.") return true end +function doPlayerSendChannelMessage(cid, author, message, SpeakClasses, channel) local p = Player(cid) return p and p:sendChannelMessage(author, message, SpeakClasses, channel) or false end +function doPlayerSetMaxCapacity(cid, cap) local p = Player(cid) return p and p:setCapacity(cap) or false end +function doPlayerSetSpecialDescription() debugPrint("Deprecated function, use Player:onLook event instead.") return true end +function doPlayerSetBalance(cid, balance) local p = Player(cid) return p and p:setBankBalance(balance) or false end +function doPlayerSetPromotionLevel(cid, level) local p = Player(cid) return p and p:setVocation(p:getVocation():getPromotion()) or false end +function doPlayerAddMoney(cid, money) local p = Player(cid) return p and p:addMoney(money) or false end +function doPlayerRemoveMoney(cid, money) local p = Player(cid) return p and p:removeMoney(money) or false end +function doPlayerTakeItem(cid, itemid, count) local p = Player(cid) return p and p:removeItem(itemid, count) or false end +function doPlayerTransferMoneyTo(cid, target, money) + if not isValidMoney(money) then + return false + end + local p = Player(cid) + return p and p:transferMoneyTo(target, money) or false +end +function doPlayerSave(cid) local p = Player(cid) return p and p:save() or false end +function doPlayerAddSoul(cid, soul) local p = Player(cid) return p and p:addSoul(soul) or false end +function doPlayerSetVocation(cid, vocation) local p = Player(cid) return p and p:setVocation(Vocation(vocation)) or false end +function doPlayerSetTown(cid, town) local p = Player(cid) return p and p:setTown(Town(town)) or false end +function setPlayerGroupId(cid, groupId) local p = Player(cid) return p and p:setGroup(Group(groupId)) or false end +doPlayerSetGroupId = setPlayerGroupId +function doPlayerSetSex(cid, sex) local p = Player(cid) return p and p:setSex(sex) or false end +function doPlayerSetGuildLevel(cid, level) local p = Player(cid) return p and p:setGuildLevel(level) or false end +function doPlayerSetGuildNick(cid, nick) local p = Player(cid) return p and p:setGuildNick(nick) or false end +function doPlayerSetOfflineTrainingSkill(cid, skillId) local p = Player(cid) return p and p:setOfflineTrainingSkill(skillId) or false end +function doShowTextDialog(cid, itemId, text) local p = Player(cid) return p and p:showTextDialog(itemId, text) or false end +function doPlayerAddItemEx(cid, uid, ...) local p = Player(cid) return p and p:addItemEx(Item(uid), ...) or false end +function doPlayerRemoveItem(cid, itemid, count, ...) local p = Player(cid) return p and p:removeItem(itemid, count, ...) or false end +function doPlayerAddPremiumDays(cid, days) local p = Player(cid) return p and p:addPremiumDays(days) or false end +function doPlayerRemovePremiumDays(cid, days) local p = Player(cid) return p and p:removePremiumDays(days) or false end +function doPlayerSetStamina(cid, minutes) local p = Player(cid) return p and p:setStamina(minutes) or false end +function doPlayerAddBlessing(cid, blessing) local p = Player(cid) return p and p:addBlessing(blessing) or false end +function doPlayerAddOutfit(cid, lookType, addons) local p = Player(cid) return p and p:addOutfitAddon(lookType, addons) or false end +function doPlayerRemOutfit(cid, lookType, addons) + local player = Player(cid) + if player == nil then + return false + end + if addons == 255 then + return player:removeOutfit(lookType) + else + return player:removeOutfitAddon(lookType, addons) + end +end +doPlayerRemoveOutfit = doPlayerRemOutfit +function canPlayerWearOutfit(cid, lookType, addons) local p = Player(cid) return p and p:hasOutfit(lookType, addons) or false end +function doPlayerSendOutfitWindow(cid) local p = Player(cid) return p and p:sendOutfitWindow() or false end +function doPlayerSendCancel(cid, text) local p = Player(cid) return p and p:sendCancelMessage(text) or false end +function doPlayerFeed(cid, food) local p = Player(cid) return p and p:feed(food) or false end +function playerLearnInstantSpell(cid, name) local p = Player(cid) return p and p:learnSpell(name) or false end +doPlayerLearnInstantSpell = playerLearnInstantSpell +function doPlayerUnlearnInstantSpell(cid, name) local p = Player(cid) return p and p:forgetSpell(name) or false end +function doPlayerPopupFYI(cid, message) local p = Player(cid) return p and p:popupFYI(message) or false end +function doSendTutorial(cid, tutorialId) local p = Player(cid) return p and p:sendTutorial(tutorialId) or false end +doPlayerSendTutorial = doSendTutorial +function doAddMapMark(cid, pos, type, description) local p = Player(cid) return p and p:addMapMark(pos, type, description or "") or false end +doPlayerAddMapMark = doAddMapMark +function doPlayerSendTextMessage(cid, type, text, ...) local p = Player(cid) return p and p:sendTextMessage(type, text, ...) or false end +function doSendAnimatedText() debugPrint("Deprecated function.") return true end +function getPlayerAccountManager() debugPrint("Deprecated function.") return true end +function doPlayerSetExperienceRate() debugPrint("Deprecated function, use Player:onGainExperience event instead.") return true end +function doPlayerSetSkillLevel(cid, skillId, value, ...) local p = Player(cid) return p and p:addSkill(skillId, value, ...) end +function doPlayerSetMagicLevel(cid, value) local p = Player(cid) return p and p:addMagicLevel(value) end +function doPlayerAddLevel(cid, amount, round) local p = Player(cid) return p and p:addLevel(amount, round) end +function doPlayerAddExp(cid, exp, useMult, ...) + local player = Player(cid) + if player == nil then + return false + end + + if useMult then + exp = exp * Game.getExperienceStage(player:getLevel()) + end + return player:addExperience(exp, ...) +end +doPlayerAddExperience = doPlayerAddExp +function doPlayerAddManaSpent(cid, mana) local p = Player(cid) return p and p:addManaSpent(mana) or false end +doPlayerAddSpentMana = doPlayerAddManaSpent +function doPlayerAddSkillTry(cid, skillid, n) local p = Player(cid) return p and p:addSkillTries(skillid, n) or false end +function doPlayerAddMana(cid, mana, ...) local p = Player(cid) return p and p:addMana(mana, ...) or false end +function doPlayerJoinParty(cid, leaderId) + local player = Player(cid) + if player == nil then + return false + end + + if player:getParty() then + player:sendTextMessage(MESSAGE_INFO_DESCR, "You are already in a party.") + return true + end + + local leader = Player(leaderId) + if leader == nil then + return false + end + + local party = leader:getParty() + if party == nil or party:getLeader() ~= leader then + return true + end + + for _, invitee in ipairs(party:getInvitees()) do + if player ~= invitee then + return true + end + end + + party:addMember(player) + return true +end +function getPartyMembers(cid) + local player = Player(cid) + if player == nil then + return false + end + + local party = player:getParty() + if party == nil then + return false + end + + local result = {party:getLeader():getId()} + for _, member in ipairs(party:getMembers()) do + result[#result + 1] = member:getId() + end + return result +end + +doPlayerSendDefaultCancel = doPlayerSendCancel + +function getMonsterTargetList(cid) + local monster = Monster(cid) + if monster == nil then + return false + end + + local result = {} + for _, creature in ipairs(monster:getTargetList()) do + if monster:isTarget(creature) then + result[#result + 1] = creature:getId() + end + end + return result +end +function getMonsterFriendList(cid) + local monster = Monster(cid) + if monster == nil then + return false + end + + local z = monster:getPosition().z + + local result = {} + for _, creature in ipairs(monster:getFriendList()) do + if not creature:isRemoved() and creature:getPosition().z == z then + result[#result + 1] = creature:getId() + end + end + return result +end +function doSetMonsterTarget(cid, target) + local monster = Monster(cid) + if monster == nil then + return false + end + + if monster:getMaster() then + return true + end + + local target = Creature(cid) + if target == nil then + return false + end + + monster:selectTarget(target) + return true +end +doMonsterSetTarget = doSetMonsterTarget +function doMonsterChangeTarget(cid) + local monster = Monster(cid) + if monster == nil then + return false + end + + if monster:getMaster() then + return true + end + + monster:searchTarget(1) + return true +end +function doCreateNpc(name, pos, ...) + local npc = Game.createNpc(name, pos, ...) return npc and npc:setMasterPos(pos) or false +end +function doSummonCreature(name, pos, ...) + local m = Game.createMonster(name, pos, ...) return m and m:getId() or false +end +doCreateMonster = doSummonCreature +function doConvinceCreature(cid, target) + local creature = Creature(cid) + if creature == nil then + return false + end + + local targetCreature = Creature(target) + if targetCreature == nil then + return false + end + + creature:addSummon(targetCreature) + return true +end +function doSummonMonster(cid, name) + local player = Player(cid) + local position = player:getPosition() + local monster = Game.createMonster(name, position) + if monster then + player:addSummon(monster) + monster:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + else + player:sendCancelMessage("There is not enough room.") + position:sendMagicEffect(CONST_ME_POFF) + end + return true +end + +function getTownId(townName) local t = Town(townName) return t and t:getId() or false end +function getTownName(townId) local t = Town(townId) return t and t:getName() or false end +function getTownTemplePosition(townId) local t = Town(townId) return t and t:getTemplePosition() or false end + +function doSetItemActionId(uid, actionId) local i = Item(uid) return i and i:setActionId(actionId) or false end +function doTransformItem(uid, newItemId, ...) local i = Item(uid) return i and i:transform(newItemId, ...) or false end +function doChangeTypeItem(uid, newType) local i = Item(uid) return i and i:transform(i:getId(), newType) or false end +function doRemoveItem(uid, ...) local i = Item(uid) return i and i:remove(...) or false end + +function getContainerSize(uid) local c = Container(uid) return c and c:getSize() or false end +function getContainerCap(uid) local c = Container(uid) return c and c:getCapacity() or false end +function getContainerItem(uid, slot) + local container = Container(uid) + if container == nil then + return pushThing(nil) + end + return pushThing(container:getItem(slot)) +end + +function doAddContainerItemEx(uid, virtualId) + local container = Container(uid) + if container == nil then + return false + end + + local res = container:addItemEx(Item(virtualId)) + if res == nil then + return false + end + return res +end + +function doSendMagicEffect(pos, magicEffect, ...) return Position(pos):sendMagicEffect(magicEffect, ...) end +function doSendDistanceShoot(fromPos, toPos, distanceEffect, ...) return Position(fromPos):sendDistanceEffect(toPos, distanceEffect, ...) end +function isSightClear(fromPos, toPos, floorCheck) return Position(fromPos):isSightClear(toPos, floorCheck) end + +function getPromotedVocation(vocationId) + local vocation = Vocation(vocationId) + if vocation == nil then + return 0 + end + + local promotedVocation = vocation:getPromotion() + if promotedVocation == nil then + return 0 + end + return promotedVocation:getId() +end +getPlayerPromotionLevel = getPromotedVocation + +function getGuildId(guildName) + local resultId = db.storeQuery("SELECT `id` FROM `guilds` WHERE `name` = " .. db.escapeString(guildName)) + if resultId == false then + return false + end + + local guildId = result.getNumber(resultId, "id") + result.free(resultId) + return guildId +end + +function getHouseName(houseId) local h = House(houseId) return h and h:getName() or false end +function getHouseOwner(houseId) local h = House(houseId) return h and h:getOwnerGuid() or false end +function getHouseEntry(houseId) local h = House(houseId) return h and h:getExitPosition() or false end +function getHouseTown(houseId) local h = House(houseId) if h == nil then return false end local t = h:getTown() return t and t:getId() or false end +function getHouseTilesSize(houseId) local h = House(houseId) return h and h:getTileCount() or false end + +function isItemStackable(itemId) return ItemType(itemId):isStackable() end +function isItemRune(itemId) return ItemType(itemId):isRune() end +function isItemDoor(itemId) return ItemType(itemId):isDoor() end +function isItemContainer(itemId) return ItemType(itemId):isContainer() end +function isItemFluidContainer(itemId) return ItemType(itemId):isFluidContainer() end +function isItemMovable(itemId) return ItemType(itemId):isMovable() end +function isCorpse(uid) local i = Item(uid) return i and ItemType(i:getId()):isCorpse() or false end + +isItemMoveable = isItemMovable +isMoveable = isMovable + +function getItemName(itemId) return ItemType(itemId):getName() end +getItemNameById = getItemName +function getItemWeight(itemId, ...) return ItemType(itemId):getWeight(...) / 100 end +function getItemDescriptions(itemId) + local itemType = ItemType(itemId) + return { + name = itemType:getName(), + plural = itemType:getPluralName(), + article = itemType:getArticle(), + description = itemType:getDescription() + } +end +function getItemIdByName(name) + local id = ItemType(name):getId() + if id == 0 then + return false + end + return id +end +function getItemWeightByUID(uid, ...) + local item = Item(uid) + if item == nil then + return false + end + + local itemType = ItemType(item:getId()) + return itemType:isStackable() and (itemType:getWeight(item:getCount(), ...) / 100) or (itemType:getWeight(1, ...) / 100) +end +function getItemRWInfo(uid) + local item = Item(uid) + if item == nil then + return false + end + + local rwFlags = 0 + local itemType = ItemType(item:getId()) + if itemType:isReadable() then + rwFlags = bit.bor(rwFlags, 1) + end + + if itemType:isWritable() then + rwFlags = bit.bor(rwFlags, 2) + end + return rwFlags +end +function getContainerCapById(itemId) return ItemType(itemId):getCapacity() end +function getFluidSourceType(itemId) local it = ItemType(itemId) return it.id ~= 0 and it:getFluidSource() or false end +function hasProperty(uid, prop) + local item = Item(uid) + if item == nil then + return false + end + + local parent = item:getParent() + if parent:isTile() and item == parent:getGround() then + return parent:hasProperty(prop) + else + return item:hasProperty(prop) + end +end + +function doSetItemText(uid, text) + local item = Item(uid) + if item == nil then + return false + end + + if text ~= "" then + item:setAttribute(ITEM_ATTRIBUTE_TEXT, text) + else + item:removeAttribute(ITEM_ATTRIBUTE_TEXT) + end + return true +end +function doSetItemSpecialDescription(uid, desc) + local item = Item(uid) + if item == nil then + return false + end + + if desc ~= "" then + item:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, desc) + else + item:removeAttribute(ITEM_ATTRIBUTE_DESCRIPTION) + end + return true +end +function doDecayItem(uid) local i = Item(uid) return i and i:decay() or false end + +function setHouseOwner(id, guid) local h = House(id) return h and h:setOwnerGuid(guid) or false end +function getHouseRent(id) local h = House(id) return h and h:getRent() or nil end +function getHouseAccessList(id, listId) local h = House(id) return h and h:getAccessList(listId) or nil end +function setHouseAccessList(id, listId, listText) local h = House(id) return h and h:setAccessList(listId, listText) or false end + +function getHouseByPlayerGUID(playerGUID) + for _, house in ipairs(Game.getHouses()) do + if house:getOwnerGuid() == playerGUID then + return house:getId() + end + end + return nil +end + +function getTileHouseInfo(pos) + local t = Tile(pos) + if t == nil then + return false + end + local h = t:getHouse() + return h and h:getId() or false +end + +function getTilePzInfo(position) + local t = Tile(position) + if t == nil then + return false + end + return t:hasFlag(TILESTATE_PROTECTIONZONE) +end + +function getTileInfo(position) + local t = Tile(position) + if t == nil then + return false + end + + local ret = pushThing(t:getGround()) + ret.protection = t:hasFlag(TILESTATE_PROTECTIONZONE) + ret.nopz = ret.protection + ret.nologout = t:hasFlag(TILESTATE_NOLOGOUT) + ret.refresh = t:hasFlag(TILESTATE_REFRESH) + ret.house = t:getHouse() ~= nil + ret.bed = t:hasFlag(TILESTATE_BED) + ret.depot = t:hasFlag(TILESTATE_DEPOT) + + ret.things = t:getThingCount() + ret.creatures = t:getCreatureCount() + ret.items = t:getItemCount() + ret.topItems = t:getTopItemCount() + ret.downItems = t:getDownItemCount() + return ret +end + +function getTileItemByType(position, itemType) + local t = Tile(position) + if t == nil then + return pushThing(nil) + end + return pushThing(t:getItemByType(itemType)) +end + +function getTileItemById(position, itemId, ...) + local t = Tile(position) + if t == nil then + return pushThing(nil) + end + return pushThing(t:getItemById(itemId, ...)) +end + +function getTileThingByPos(position) + local t = Tile(position) + if t == nil then + if position.stackpos == -1 then + return -1 + end + return pushThing(nil) + end + + if position.stackpos == -1 then + return t:getThingCount() + end + return pushThing(t:getThing(position.stackpos)) +end + +function getTileThingByTopOrder(position, topOrder) + local t = Tile(position) + if t == nil then + return pushThing(nil) + end + return pushThing(t:getItemByTopOrder(topOrder)) +end + +function getTopCreature(position) + local t = Tile(position) + if t == nil then + return pushThing(nil) + end + return pushThing(t:getTopCreature()) +end + +function queryTileAddThing(thing, position, ...) local t = Tile(position) return t and t:queryAdd(thing, ...) or false end + +function doTeleportThing(uid, dest, pushMovement) + if type(uid) == "userdata" then + if uid:isCreature() then + return uid:teleportTo(dest, pushMovement or false) + else + return uid:moveTo(dest) + end + else + if uid >= 0x10000000 then + local creature = Creature(uid) + if creature then + return creature:teleportTo(dest, pushMovement or false) + end + else + local item = Item(uid) + if item then + return item:moveTo(dest) + end + end + end + return false +end + +function getThingPos(uid) + local thing + if type(uid) ~= "userdata" then + if uid >= 0x10000000 then + thing = Creature(uid) + else + thing = Item(uid) + end + else + thing = uid + end + + if thing == nil then + return false + end + + local stackpos = 0 + local tile = thing:getTile() + if tile then + stackpos = tile:getThingIndex(thing) + end + + local position = thing:getPosition() + position.stackpos = stackpos + return position +end +getThingPosition = getThingPos + +function getThingfromPos(pos) + local tile = Tile(pos) + if tile == nil then + return pushThing(nil) + end + + local thing + local stackpos = pos.stackpos or 0 + if stackpos == STACKPOS_TOP_MOVEABLE_ITEM_OR_CREATURE then + thing = tile:getTopCreature() + if thing == nil then + local item = tile:getTopDownItem() + if item and item:getType():isMovable() then + thing = item + end + end + elseif stackpos == STACKPOS_TOP_FIELD then + thing = tile:getFieldItem() + elseif stackpos == STACKPOS_TOP_CREATURE then + thing = tile:getTopCreature() + else + thing = tile:getThing(stackpos) + end + return pushThing(thing) +end + +function doRelocate(fromPos, toPos) + if fromPos == toPos then + return false + end + + local fromTile = Tile(fromPos) + if fromTile == nil then + return false + end + + if Tile(toPos) == nil then + return false + end + + for i = fromTile:getThingCount() - 1, 0, -1 do + local thing = fromTile:getThing(i) + if thing then + if thing:isItem() then + if ItemType(thing:getId()):isMovable() then + thing:moveTo(toPos) + end + elseif thing:isCreature() then + thing:teleportTo(toPos) + end + end + end + return true +end + +function getThing(uid) + return uid >= 0x10000000 and pushThing(Creature(uid)) or pushThing(Item(uid)) +end + +function getConfigInfo(info) + if type(info) ~= "string" then + return nil + end + dofile('config.lua') + return _G[info] +end + +function getWorldCreatures(type) + if type == 0 then + return Game.getPlayerCount() + elseif type == 1 then + return Game.getMonsterCount() + elseif type == 2 then + return Game.getNpcCount() + end + return Game.getPlayerCount() + Game.getMonsterCount() + Game.getNpcCount() +end + +saveData = saveServer + +function getGlobalStorageValue(key) + return Game.getStorageValue(key) or -1 +end +getStorage = getGlobalStorageValue +function setGlobalStorageValue(key, value) + Game.setStorageValue(key, value) + return true +end +doSetStorage = setGlobalStorageValue +getWorldType = Game.getWorldType + +function setWorldType(type) + return Game.setWorldType(type) +end + +function getGameState() + return Game.getGameState() +end + +function doSetGameState(state) + return Game.setGameState(state) +end + +function doExecuteRaid(raidName) + return Game.startRaid(raidName) +end + +numberToVariant = Variant +stringToVariant = Variant +positionToVariant = Variant + +function targetPositionToVariant(position) + local variant = Variant(position) + variant.type = VARIANT_TARGETPOSITION + return variant +end + +variantToNumber = Variant.getNumber +variantToString = Variant.getString +variantToPosition = Variant.getPosition + +function doCreateTeleport(itemId, destination, position) + local item = Game.createItem(itemId, 1, position) + if not item:isTeleport() then + item:remove() + return false + end + item:setDestination(destination) + return item:getUniqueId() +end + +function getSpectators(centerPos, rangex, rangey, multifloor, onlyPlayers) + local result = Game.getSpectators(centerPos, multifloor, onlyPlayers or false, rangex, rangex, rangey, rangey) + if #result == 0 then + return nil + end + + for index, spectator in ipairs(result) do + result[index] = spectator:getId() + end + return result +end + +function broadcastMessage(message, messageType) + Game.broadcastMessage(message, messageType) + print("> Broadcasted message: \"" .. message .. "\".") +end +doBroadcastMessage = broadcastMessage + +function Guild.addMember(self, player) + return player:setGuild(self) +end +function Guild.removeMember(self, player) + return player:getGuild() == self and player:setGuild(nil) +end + +function getPlayerInstantSpellCount(cid) local p = Player(cid) return p and #p:getInstantSpells() end +function getPlayerInstantSpellInfo(cid, spellId) + local player = Player(cid) + if not player then + return false + end + + local spell = Spell(spellId) + if not spell or not player:canCast(spell) then + return false + end + + return spell +end + +function doSetItemOutfit(cid, item, time) local c = Creature(cid) return c and c:setItemOutfit(item, time) end +function doSetMonsterOutfit(cid, name, time) local c = Creature(cid) return c and c:setMonsterOutfit(name, time) end +function doSetCreatureOutfit(cid, outfit, time) + local creature = Creature(cid) + if not creature then + return false + end + + local condition = Condition(CONDITION_OUTFIT) + condition:setOutfit(outfit) + condition:setTicks(time) + creature:addCondition(condition) + + return true +end + +function doTileAddItemEx(pos, uid, flags) + local tile = Tile(pos) + if not tile then + return false + end + + local item = Item(uid) + if item then + return tile:addItemEx(item, flags) + end + + return false +end + +function isInArray(array, value) return table.contains(array, value) end + +function doCreateItem(itemid, count, pos) + local tile = Tile(pos) + if not tile then + return false + end + + local item = Game.createItem(itemid, count, pos) + if item then + return item:getUniqueId() + end + return false +end + +function doCreateItemEx(itemid, count) + local item = Game.createItem(itemid, count) + if item then + return item:getUniqueId() + end + return false +end + +function doMoveCreature(cid, direction) local c = Creature(cid) return c ~= nil and c:move(direction) end + +function createFunctions(class) + local exclude = {[2] = {"is"}, [3] = {"get", "set", "add", "can"}, [4] = {"need"}} + local temp = {} + for name, func in pairs(class) do + local add = true + for strLen, strTable in pairs(exclude) do + if table.contains(strTable, name:sub(1, strLen)) then + add = false + end + end + if add then + local str = name:sub(1, 1):upper() .. name:sub(2) + local getFunc = function(self) return func(self) end + local setFunc = function(self, ...) return func(self, ...) end + local get = "get" .. str + local set = "set" .. str + if not (rawget(class, get) and rawget(class, set)) then + table.insert(temp, {set, setFunc, get, getFunc}) + end + end + end + for _, func in ipairs(temp) do + rawset(class, func[1], func[2]) + rawset(class, func[3], func[4]) + end +end + +function isNumber(str) + return tonumber(str) ~= nil +end + +function doSetCreatureLight(cid, lightLevel, lightColor, time) + local creature = Creature(cid) + if not creature then + return false + end + + local condition = Condition(CONDITION_LIGHT) + condition:setParameter(CONDITION_PARAM_LIGHT_LEVEL, lightLevel) + condition:setParameter(CONDITION_PARAM_LIGHT_COLOR, lightColor) + condition:setTicks(time) + creature:addCondition(condition) + return true +end + +function getExperienceForLevel(level) return Game.getExperienceForLevel(level) end + +do + local combats = { + [COMBAT_PHYSICALDAMAGE] = 'physical', + [COMBAT_ENERGYDAMAGE] = 'energy', + [COMBAT_EARTHDAMAGE] = 'earth', + [COMBAT_FIREDAMAGE] = 'fire', + [COMBAT_UNDEFINEDDAMAGE] = 'undefined', + [COMBAT_LIFEDRAIN] = 'lifedrain', + [COMBAT_MANADRAIN] = 'manadrain', + [COMBAT_HEALING] = 'healing', + [COMBAT_DROWNDAMAGE] = 'drown', + [COMBAT_ICEDAMAGE] = 'ice', + [COMBAT_HOLYDAMAGE] = 'holy', + [COMBAT_DEATHDAMAGE] = 'death' + } + + function getCombatName(combat) + return combats[combat] + end +end + +do + local skills = { + [SKILL_FIST] = 'fist fighting', + [SKILL_CLUB] = 'club fighting', + [SKILL_SWORD] = 'sword fighting', + [SKILL_AXE] = 'axe fighting', + [SKILL_DISTANCE] = 'distance fighting', + [SKILL_SHIELD] = 'shielding', + [SKILL_FISHING] = 'fishing', + [SKILL_MAGLEVEL] = 'magic level', + [SKILL_LEVEL] = 'level' + } + + function getSkillName(skill) + return skills[skill] or 'unknown' + end +end + +do + local specialSkills = { + [SPECIALSKILL_CRITICALHITCHANCE] = 'critical hit chance', + [SPECIALSKILL_CRITICALHITAMOUNT] = 'critical extra damage', + [SPECIALSKILL_LIFELEECHCHANCE] = 'hitpoints leech chance', + [SPECIALSKILL_LIFELEECHAMOUNT] = 'hitpoints leech amount', + [SPECIALSKILL_MANALEECHCHANCE] = 'manapoints leech chance', + [SPECIALSKILL_MANALEECHAMOUNT] = 'manapoints leech amount' + } + + function getSpecialSkillName(specialSkill) + return specialSkills[specialSkill] or 'unknown' + end +end + +function indexToCombatType(idx) + return bit.lshift(1, idx) +end + +function showpos(v) + return v > 0 and '+' or '-' +end + +-- this is a fix for lua52 or higher which has the function renamed to table.unpack, while luajit still uses unpack +if unpack == nil then unpack = table.unpack end diff --git a/data/lib/core/achievements.lua b/data/lib/core/achievements.lua new file mode 100644 index 0000000..42539ab --- /dev/null +++ b/data/lib/core/achievements.lua @@ -0,0 +1,666 @@ +--[[ +Functions: + getAchievementInfoById(achievement_id) + getAchievementInfoByName(achievement_name) + getSecretAchievements() + getPublicAchievements() + getAchievements() + Player:addAchievement(achievement_id/name[, hideMsg]) + Player:removeAchievement(achievement_id/name) + Player:hasAchievement(achievement_id/name) + Player:addAllAchievements([hideMsg]) + Player:removeAllAchievements() + Player:getSecretAchievements() + Player:getPublicAchievements() + Player:getAchievements() + isAchievementSecret(achievement_id/name) + Player:getAchievementPoints() + Player:addAchievementProgress() +Storages: + PlayerStorageKeys.achievementsBase -- base storage + PlayerStorageKeys.achievementsCounter -- this storage will be used to save the process to obtain the certain achievement + (Ex: this storage + the id of achievement 'Allowance Collector' to save how many piggy banks has been broken +]] + +achievements = +{ + -- 8.6 + [1] = {name = "Allow Cookies?", grade = 1, points = 2, description = "With a perfectly harmless smile you fooled all of those wisecrackers into eating your exploding cookies. Consider a boy or girl scout outfit next time to make the trick even better."}, + [2] = {name = "Allowance Collector", grade = 1, points = 2, secret = true, description = "You certainly have your ways when it comes to acquiring money. Many of them are pink and paved with broken fragments of porcelain."}, + [3] = {name = "Amateur Actor", grade = 1, points = 2, description = "You helped bringing Princess Buttercup, Doctor Dumbness and Lucky the Wonder Dog to life - and will probably dream of them tonight, since you memorised your lines perfectly. What a .. special piece of.. screenplay."}, + [4] = {name = "Animal Activist", grade = 1, points = 2, description = "You have a soft spot for little, weak animals, and you do everything in your power to protect them - even if you probably eat dragons for breakfast."}, + [5] = {name = "Annihilator", grade = 2, points = 5, description = "You've daringly jumped into the infamous Annihilator and survived - taking home fame, glory and your reward."}, + [6] = {name = "Archpostman", grade = 1, points = 3, description = "Delivering letters and parcels has always been a secret passion of yours, and now you can officially put on your blue hat, blow your Post Horn and do what you like to do most. Beware of dogs!"}, + [7] = {name = "Backpack Tourist", grade = 1, points = 1, secret = true, description = "If someone lost a random thing in a random place, you're probably a good person to ask and go find it, even if you don't know what and where."}, + [8] = {name = "Beach Tamer", grade = 1, points = 2, description = "You re-enacted the Taming of the Shrew on a beach setting and proved that you can handle capricious girls quite well. With or without fish tails."}, + [9] = {name = "Bearhugger", grade = 1, points = 1, description = "Warm, furry and cuddly - though that same bear you just hugged would probably rip you into pieces if he had been conscious, he reminded you of that old teddy bear which always slept in your bed when you were still small."}, + [10] = {name = "Blessed!", grade = 1, points = 2, description = "You travelled the world for an almost meaningless prayer - but at least you don't have to do that again and can get a new blessed stake in the blink of an eye."}, + [11] = {name = "Bone Brother", grade = 1, points = 1, description = "You've joined the undead bone brothers - making death your enemy and your weapon as well. Devouring what's weak and leaving space for what's strong is your primary goal."}, + [12] = {name = "Castlemania", grade = 2, points = 5, secret = true, description = "You have an eye for suspicious places and love to read other people's diaries, especially those with vampire stories in it. You're also a dedicated token collector and explorer. Respect!"}, + [13] = {name = "Champion of Chazorai", grade = 2, points = 4, description = "You won the merciless 2 vs. 2 team tournament on the Isle of Strife and wiped out wave after wave of fearsome opponents. Death or victory - you certainly chose the latter."}, + [14] = {name = "Chorister", grade = 1, points = 1, description = "Lalalala... you now know the cult's hymn sung in Liberty Bay"}, + [15] = {name = "Clay Fighter", grade = 1, points = 3, secret = true, description = "You love getting your hands wet and dirty - and covered with clay. Your perfect sculpture of Brog, the raging Titan is your true masterpiece."}, + [16] = {name = "Clay to Fame", grade = 2, points = 6, secret = true, description = "Sculpting Brog, the raging Titan, is your secret passion. Numerous perfect little clay statues with your name on them can be found everywhere around the World."}, + [17] = {name = "Cold as Ice", grade = 2, points = 6, secret = true, description = "Take an ice cube and an obsidian knife and you'll very likely shape something really pretty from it. Mostly cute little mammoths, which are a hit with all the girls."}, + [18] = {name = "Culinary Master", grade = 2, points = 4, description = "Simple hams and bread merely make you laugh. You're the master of the extra-ordinaire, melter of cheese, fryer of bat wings and shaker of shakes. Delicious!"}, + [19] = {name = "Deep Sea Diver", grade = 2, points = 4, secret = true, description = "Under the sea - might not be your natural living space, but you're feeling quite comfortable on the ocean floor. Quara don't scare you anymore and sometimes you sleep with your helmet of the deep still equipped."}, + [20] = {name = "Dread Lord", grade = 3, points = 8, secret = true, description = "You don't care for rules that others set up and shape the world to your liking. Having left behind meaningless conventions and morals, you prize only the power you wield. You're a master of your fate and battle to cleanse the world."}, + [21] = {name = "Efreet Ally", grade = 1, points = 3, description = "Even though the welcomed you only reluctantly and viewed you as \"only a human\" for quite some time, you managed to impress Malor and gained his respect and trade options with the green djinns."}, + [22] = {name = "Elite Hunter", grade = 2, points = 5, description = "You jump at every opportunity for a hunting challenge that's offered to you and carry out those tasks with deadly precision. You're a hunter at heart and a valuable member of the Paw & Fur Society."}, + [23] = {name = "Explorer", grade = 2, points = 4, description = "You've been to places most people don't even know the names of. Collecting botanic, zoologic and ectoplasmic samples is your daily business and you're always prepared to discover new horizons."}, + [24] = {name = "Exquisite Taste", grade = 1, points = 2, secret = true, description = "You love fish - but preferably those caught in the cold north. Even though they're hard to come by you never get tired of picking holes in ice sheets and hanging your fishing rod in."}, + [25] = {name = "Firewalker", grade = 2, points = 4, secret = true, description = "Running barefoot across ember is not for you! You do it the elegant way. Yet, you're kind of drawn to fire and warm surroundings in general - you like it hot!"}, + [26] = {name = "Fireworks in the Sky", grade = 1, points = 2, secret = true, description = "You love the moment right before your rocket takes off and explodes into beautiful colours - not only on new year's eve!"}, + [27] = {name = "Follower of Azerus", grade = 2, points = 4, description = "When you do something, you do it right. You have an opinion and you stand by it - and no one will be able to convince you otherwise. On a sidenote, you're a bit on the brutal and war-oriented side, but that's not a bad thing, is it?"}, + [28] = {name = "Follower of Palimuth", grade = 2, points = 4, description = "You're a peacekeeper and listen to what the small people have to say. You've made up your mind and know who to help and for which reasons - and you do it consistently. Your war is fought with reason rather than weapons."}, + [29] = {name = "Fountain of Life", grade = 1, points = 1, secret = true, description = "You found and took a sip from the Fountain of Life. Thought it didn't grant you eternal life, you feel changed and somehow at peace."}, + [30] = {name = "Friend of the Apes", grade = 2, points = 4, description = "You know Banuta like the back of your hand and are good at destroying caskets and urns. The sight of giant footprints doesn't keep you from exploring unknown areas either."}, + [31] = {name = "Ghostwhisperer", grade = 1, points = 3, description = "You don't hunt them, you talk to them. You know that ghosts might keep secrets that have been long lost among the living, and you're skilled at talking them into revealing them to you."}, + [32] = {name = "Golem in the Gears", grade = 2, points = 4, description = "You're an aspiring mago-mechanic. Science and magic work well together in your eyes - and even though you probably delivered countless wrong charges while working for Telas, you might just have enough knowledge to build your own golem now."}, + [33] = {name = "Green Thumb", grade = 2, points = 4, secret = true, description = "If someone gives you seeds, you usually grow a beautiful plant from it within a few days. You like your house green and decorated with flowers. Probably you also talk to them."}, + [34] = {name = "Greenhorn", grade = 1, points = 2, description = "You wiped out Orcus the Cruel in the Arena of Svargrond. You're still a bit green behind the ears, but there's some great potential."}, + [35] = {name = "Herbicide", grade = 3, points = 8, secret = true, description = "You're one of the brave heroes to face and defeat the mysterious demon oak and all the critters it threw in your face. Wielding your blessed axe no tree dares stand in your way - demonic or not."}, + [36] = {name = "Here, Fishy Fishy!", grade = 1, points = 1, secret = true, description = "Ah, the smell of the sea! Standing at the shore and casting a line is one of your favourite activities. For you, fishingis relaxing - and at the same time, providing easy food. Perfect!"}, + [37] = {name = "High-Flyer", grade = 2, points = 4, secret = true, description = "The breeze in your hair, your fingers clutching the rim of your Carpet - that's how you like to travel. Faster! Higher! And a looping every now and then."}, + [38] = {name = "High Inquisitor", grade = 2, points = 5, description = "You're the one who poses the questions around here, and you know how to get the answers you want to hear. Besides, you're a famous exorcist and slay a few vampires and demons here and there. You and your stake are a perfect team."}, + [39] = {name = "His True Face", grade = 1, points = 3, secret = true, description = "You're one of the few citizens who Armenius chose to actually show his true face to - and he made you fight him. Either that means you're very lucky or very unlucky, but one thing's for sure - it's extremely rare."}, + [40] = {name = "Honorary Barbarian", grade = 1, points = 1, description = "You've hugged bears, pushed mammoths and proved your drinking skills. And even though you have a slight hangover, a partially fractured rib and some greasy hair on your tongue, you're quite proud to call yourself a honorary barbarian from now on."}, + [41] = {name = "Huntsman", grade = 1, points = 2, description = "You're familiar with hunting tasks and have carried out quite a few already. A bright career as hunter for the Paw & Fur society lies ahead!"}, + [42] = {name = "Ice Sculptor", grade = 1, points = 3, secret = true, description = "You love to hang out in cold surroundings and consider ice the best material to be shaped. What a waste to use ice cubes for drinks when you can create a beautiful mammoth statue from it!"}, + [43] = {name = "Interior Decorator", grade = 2, points = 4, secret = true, description = "Your home is your castle - and the furniture in it is just as important. Your friends ask for your advice when decorating their Houses and your probably own every statue, rack and bed there is."}, + [44] = {name = "Jamjam", grade = 2, points = 5, secret = true, description = "When it comes to interracial understanding, you're an expert. You've mastered the language of the Chakoya and made someone really happy with your generosity. Achuq!"}, + [45] = {name = "Jinx", grade = 1, points = 2, secret = true, description = "Sometimes you feel there's a gremlin in there. So many lottery tickets, so many blanks? That's just not fair! Share your misery with the world."}, + [46] = {name = "Just in Time", grade = 1, points = 1, description = "You're a fast runner and are good at delivering wares which are bound to decay just in the nick of time, even if you can't use any means of transportation or if your hands get cold or smelly in the process."}, + [47] = {name = "King Fan", grade = 1, points = 3, description = "You're not sure what it is, but you feel drawn to royalty. Your knees are always a bit grazed from crawling around in front of thrones and you love hanging out in castles. Maybe you should consider applying as a guard?"}, + [48] = {name = "Lord Protector", grade = 3, points = 8, secret = true, description = "You proved yourself - not only in your dreams - and possess a strong and spiritual mind. Your valorous fight against demons and the undead plague has granted you the highest and most respected rank among the Nightmare Knights."}, + [49] = {name = "Lord of the Elements", grade = 2, points = 5, description = "You travelled the surreal realm of the elemental spheres, summoned and slayed the Lord of the Elements, all in order to retrieve neutral matter. And as brave as you were, you couldn't have done it without your team!"}, + [50] = {name = "Lucid Dreamer", grade = 1, points = 2, description = "Dreams - are your reality? Strange visions, ticking clocks, going to bed and waking up somewhere completely else - that was some trip, but you're almost sure you actually did enjoy it."}, + [51] = {name = "Lucky Devil", grade = 2, points = 4, secret = true, description = "That's almost too much luck for one person. If something's really, really rare - it probably falls into your lap sooner or later. Congratulations!"}, + [52] = {name = "Marble Madness", grade = 2, points = 6, secret = true, description = "Your little statues of Godsula have become quite famous around the World and there's few people with similar skills when it comes to shaping marble."}, + [53] = {name = "Marblelous", grade = 1, points = 3, secret = true, description = "You're an aspiring marble sculptor with promising skills - proven by the perfect little Godsula statue you shaped. One day you'll be really famous!"}, + [54] = {name = "Marid Ally", grade = 1, points = 3, description = "You've proven to be a valuable ally to the Marid, and Gabel welcomed you to trade with Haroun and Nah'Bob whenever you want to. Though the Djinn war has still not ended, the Marid can't fail with you on their side."}, + [55] = {name = "Masquerader", grade = 1, points = 3, secret = true, description = "You probably don't know anymore how you really look like - usually when you look into a mirror, some kind of monster stares back at you. On the other hand - maybe that's an improvement?"}, + [56] = {name = "Master Thief", grade = 2, points = 4, description = "Robbing, inviting yourself to VIP parties, faking contracts and pretending to be someone else - you're a jack of all trades when it comes to illegal activities. You take no prisoners, except for the occasional goldfish now and then."}, + [57] = {name = "Master of the Nexus", grade = 2, points = 6, description = "You were able to fight your way through the countless hordes in the Demon Forge. Once more you proved that nothing is impossible."}, + [58] = {name = "Matchmaker", grade = 1, points = 1, description = "You don't believe in romance to be a coincidence or in love at first sight. In fact - love potions, bouquets of flowers and cheesy poems do the trick much better than ever could. Keep those hormones flowing!"}, + [59] = {name = "Mathemagician", grade = 1, points = 1, description = "Sometimes the biggest secrets of life can have a simple solution."}, + [60] = {name = "Ministrel", grade = 1, points = 2, secret = true, description = "You can handle any music instrument you're given - and actually manage to produce a pleasant sound with it. You're a welcome guest and entertainer in most taverns."}, + [61] = {name = "Nightmare Knight", grade = 1, points = 1, description = "You follow the path of dreams and that of responsibility without self-centered power. Free from greed and selfishness, you help others without expecting a reward."}, + [62] = {name = "Party Animal", grade = 1, points = 1, secret = true, description = "Oh my god, it's a paaaaaaaaaaaarty! You're always in for fun, friends and booze and love being the center of attention. There's endless reasons to celebrate! Woohoo!"}, + [63] = {name = "Passionate Kisser", grade = 1, points = 3, description = "For you, a kiss is more than a simple touch of lips. You kiss maidens and deadbeats alike with unmatched affection and faced death and rebirth through the kiss of the banshee queen. Lucky are those who get to share such an intimate moment with you!"}, + [64] = {name = "Perfect Fool", grade = 1, points = 3, description = "You love playing jokes on others and tricking them into looking a little silly. Wagging tongues say that the moment of realisation in your victims' eyes is the reward you feed on, but you're probably just kidding and having fun with them... right??"}, + [65] = {name = "Poet Laureate", grade = 1, points = 2, secret = true, description = "Poems, verses, songs and rhymes you've recited many times. You have passed the cryptic door, raconteur of ancient lore. Even elves you've left impressed, so it seems you're truly blessed."}, + [66] = {name = "Polisher", grade = 2, points = 4, secret = true, description = "If you see a rusty item, you can't resist polishing it. There's always a little flask of rust remover in your inventory - who knows, there might be a golden armor beneath all that dirt!"}, + [67] = {name = "Potion Addict", grade = 2, points = 4, secret = true, description = "Your local magic trader considers you one of his best customers - you usually buy large stocks of potions so you won't wake up in the middle of the night craving for more. Yet, you always seem to run out of them too fast. Cheers!"}, + [68] = {name = "Quick as a Turtle", grade = 1, points = 2, secret = true, description = "There... is... simply... no... better... way - than to travel on the back of a turtle. At least you get to enjoy the beautiful surroundings of Laguna."}, + [69] = {name = "Razing!", grade = 3, points = 7, secret = true, description = "People with sharp canine teeth better beware of you, especially at nighttime, or they might find a stake between their ribs. You're a merciless vampire hunter and have gathered numerous tokens as proof."}, + [70] = {name = "Recognised Trader", grade = 1, points = 3, description = "You're a talented merchant who's able to handle wares with care, finds good offers and digs up rares every now and then. Never late to complete an order, you're a reliable trader - at least in Rashid's eyes."}, + [71] = {name = "Rockstar", grade = 1, points = 3, secret = true, description = "Music just comes to you naturally. You feel comfortable on any stage, at any time, and secretly hope that someday you will be able to defeat your foes by playing music only. Rock on!"}, + [72] = {name = "Ruthless", grade = 2, points = 5, description = "You've touched all thrones of The Ruthless Seven and absorbed some of their evil spirit. It may have changed you forever."}, + [73] = {name = "Scrapper", grade = 1, points = 3, description = "You put out the Spirit of Fire's flames in the arena of Svargrond. Arena fights are for you - fair, square, with simple rules and one-on-one battles."}, + [74] = {name = "Sea Scout", grade = 1, points = 2, description = "Not even the hostile underwater environment stops you from doing your duty for the Explorer Society. Scouting the Quara realm is a piece of cake for you."}, + [75] = {name = "Secret Agent", grade = 1, points = 1, description = "Pack your spy gear and get ready for some dangerous missions in service of a secret agency. You've shown you want to - but can you really do it? Time will tell."}, + [76] = {name = "Shell Seeker", grade = 1, points = 3, secret = true, description = "You found a hundred beautiful pearls in large sea shells. By now that necklace should be finished - and hopefully you didn't get your fingers squeezed too often during the process."}, + [77] = {name = "Ship's Kobold", grade = 2, points = 4, secret = true, description = "You've probably never gotten seasick in your life - you love spending your free time on the ocean and covered quite a lot of miles with ships. Aren't you glad you didn't have to swim all that?"}, + [78] = {name = "Steampunked", grade = 1, points = 2, secret = true, description = "Travelling with the dwarven steamboats through the underground rivers is your preferred way of crossing the lands. No pesky seagulls, and good beer on board!"}, + [79] = {name = "Superstitious", grade = 1, points = 2, secret = true, description = "Fortune tellers and horoscopes guide you through your life. And you probably wouldn't dare going on a big game hunt without your trusty voodoo skull giving you his approval for the day."}, + [80] = {name = "Talented Dancer", grade = 1, points = 1, description = "You're a lord or lady of the dance - and not afraid to use your skills to impress tribal gods. One step to the left, one jump to the right, twist and shout!"}, + [81] = {name = "Territorial", grade = 1, points = 1, secret = true, description = "Your map is your friend - always in your back pocket and covered with countless marks of interesting and useful locations. One could say that you might be lost without it - but luckily there's no way to take it from you."}, + [82] = {name = "The Milkman", grade = 1, points = 2, description = "Who's the milkman? You are!"}, + [83] = {name = "Top AVIN Agent", grade = 2, points = 4, description = "You've proven yourself as a worthy member of the 'family' and successfully carried out numerous spy missions for your 'uncle' to support the Venorean traders and their goals."}, + [84] = {name = "Top CGB Agent", grade = 2, points = 4, description = "Girl power! Whether you're female or not, you've proven absolute loyalty and the willingness to put your life at stake for the girls brigade of Carlin."}, + [85] = {name = "Top TBI Agent", grade = 2, points = 4, description = "Conspiracies and open secrets are your daily bread. You've shown loyalty to the Thaian crown through your courage when facing enemies and completing spy missions. You're an excellent field agent of the TBI."}, + [86] = {name = "Turncoat", grade = 2, points = 4, secret = true, description = "You served Yalahar - but you didn't seem so sure whom to believe on the way. Both Azerus and Palimuth had good reasons for their actions, and thus you followed your gut instinct in the end, even if you helped either of them. May Yalahar prosper!"}, + [87] = {name = "Vanity", grade = 1, points = 3, secret = true, description = "Aren't you just perfectly, wonderfully, beautifully gorgeous? You can't pass a mirror without admiring your looks. Or maybe doing a quick check whether something's stuck in your teeth, perhaps?"}, + [88] = {name = "Vive la Resistance", grade = 1, points = 2, description = "You've always been a rebel - admit it! Supplying prisoners, caring for outcasts, stealing from the rich and giving to the poor - no wait, that was another story."}, + [89] = {name = "Warlord of Svargrond", grade = 2, points = 5, description = "You sent the Obliverator into oblivion in the arena of Svargrond and defeated nine other dangerous enemies on the way. All hail the Warlord of Svargrond!"}, + [90] = {name = "Waverider", grade = 1, points = 2, secret = true, description = "One thing's for sure: You definitely love swimming. Hanging out on the beach with your friends, having ice cream and playing beach ball is splashingly good fun!"}, + [91] = {name = "Wayfarer", grade = 1, points = 3, secret = true, description = "Dragon dreams are golden."}, + [92] = {name = "Worm Whacker", grade = 1, points = 1, secret = true, description = "Weehee! Whack those worms! You sure know how to handle a big hammer."}, + + -- 8.61 + [93] = {name = "Cocoon of Doom", grade = 1, points = 3, secret = true, description = "You helped bringing Devovorga's dangerous tentacles and her humongous cocoon down - not stopping her transformation, but ultimately completing a crucial step to her death."}, + [94] = {name = "Daring Trespasser", grade = 1, points = 3, secret = true, description = "You've entered the lair of Devovorga and joined the crew trying to take her down - whether crowned with success or not doesn't matter, but they can't blame you for not trying!"}, + [95] = {name = "Devovorga's Nemesis", grade = 2, points = 5, secret = true, description = "One special hero among many. This year - it was you. Devovorga withdrew in a darker realm because she could not withstand your power - and that of your comrades. Time will tell if the choice you made was good - but for now, it saved your world."}, + [96] = {name = "I Did My Part", grade = 1, points = 2, secret = true, description = "Your world is lucky to have you! You don't hesitate to jump in and help when brave heroes are called to save the world."}, + [97] = {name = "Notorious Worldsaver", grade = 3, points = 8, secret = true, description = "You're in the front line when it comes to saving your world or taking part in social events. Whether you do it noticed or unnoticed by the people, your world can rely on you to dutifully do your part to make it a better place for everyone."}, + [98] = {name = "Slayer of Anmothra", grade = 1, points = 2, secret = true, description = "Souls are like butterflies. The black soul of a living weapon yearning to strike lies shattered beneath your feet."}, + [99] = {name = "Slayer of Chikhaton", grade = 1, points = 2, secret = true, description = "Power lies in the will of her who commands it. You fought it with full force - and were stronger."}, + [100] = {name = "Slayer of Irahsae", grade = 1, points = 2, secret = true, description = "Few things equal the wild fury of a trapped and riven creature. You were a worthy opponent."}, + [101] = {name = "Slayer of Phrodomo", grade = 1, points = 2, secret = true, description = "Blind hatred took physical form, violently rebelling against the injustice it was born into. You were not able to bring justice - but at least temporary peace."}, + [102] = {name = "Slayer of Teneshpar", grade = 1, points = 2, secret = true, description = "The forbidden knowledge of aeons was never meant to invade this world. You silenced its voice before it could be made heard."}, + [103] = {name = "Teamplayer", grade = 1, points = 2, secret = true, description = "You don't consider yourself too good to do the dirty work while someone else might win the laurels for killing Devovorga. They couldn't do it without you!"}, + + -- 8.62 + [104] = {name = "Alumni", grade = 2, points = 6, description = "You're considered a first-rate graduate of the Magic Academy in Edron due to your pioneering discoveries and successful studies in the field of experimental magic and spell development. Ever considered teaching the Armageddon spell?"}, + [105] = {name = "Aristocrat", grade = 2, points = 4, description = "You begin your day by bathing in your pot of gold and you don't mind showing off your wealth while strolling the streets in your best clothes - after all it's your hard-earned money! You prefer to be addressed with 'Your Highness'."}, + [106] = {name = "Bad Timing", grade = 1, points = 2, secret = true, description = "Argh! Not now! How is it that those multifunctional tools never fail when you're using them for something completely trivial like squeezing juice, but mess up when you desperately need to climb up a rope spot with a fire-breathing dragon chasing you?"}, + [107] = {name = "Berserker", grade = 1, points = 3, description = "RAWR! Strength running through your body, your heart racing faster and adrenaline fueling your every weapon swing. All in a little bottle. No refund for destroyed furniture. For further questions consult your healer or potion dealer."}, + [108] = {name = "Bluebarian", grade = 1, points = 2, secret = true, description = "You live the life of hunters and gatherers. Well, especially that of a gatherer, and especially of one who gathers lots of blueberries. Have you checked the colour of your tongue lately?"}, + [109] = {name = "Brutal Politeness", grade = 2, points = 6, description = "What is best in life? To crush your enemies. To see them driven before you. And to maybe have a nice cup of tea afterwards."}, + [110] = {name = "Commitment Phobic", grade = 1, points = 2, secret = true, description = "Longterm relationships are just not for you. And each time you think you're in love, you're proven wrong shortly afterwards. Or maybe you just end up with the wrong lover each time - exploited and betrayed. Staying single might just be better."}, + [111] = {name = "Cookie Monster", grade = 1, points = 1, secret = true, description = "You can easily be found by anyone if they just follow the cookie crumb trail. And for you, true love means to give away your last cookie."}, + [112] = {name = "Cursed!", grade = 1, points = 3, secret = true, description = "The wrath of the Noxious Spawn - you accidentally managed to incur it. Your days are counted and your death inevitable. Sometime. Someplace."}, + [113] = {name = "Demonbane", grade = 2, points = 6, description = "You don't carry that stake just for decoration - you're prepared to use it. Usually you're seen hightailing through the deepest dungeons leaving a trail of slain demons. Whoever dares stand in your way should prepare to die."}, + [114] = {name = "Demonic Barkeeper", grade = 1, points = 3, description = "Thick, red - shaken, not stirred - and with a straw in it: that's the way you prefer your demon blood. Served with an onion ring, the subtle metallic aftertaste is almost not noticeable. Beneficial effects on health or mana are welcome."}, + [115] = {name = "The Snowman", grade = 1, points = 1, description = "You love the winter. Fully equipped with scarf and gloves, you like to have fun outside while building lots of snowmen with your friends. Snowball fight, anyone?"}, + [116] = {name = "Do Not Disturb", grade = 1, points = 1, secret = true, description = "Urgh! Close the windows! Shut out the sun rearing its ugly yellow head, shut out the earsplitting laughter of your neighbour's corpulent children. Ahhh. Embrace sweet darkness and silence."}, + [117] = {name = "Exemplary Citizen", grade = 2, points = 4, description = "Every city should be proud to call someone like you its inhabitant. You're keeping the streets clean and help settling the usual disputes in front of the depot. Also, you probably own a cat and like hiking."}, + [118] = {name = "Fool at Heart", grade = 1, points = 3, description = "And remember: Never try to teach a pig to sing. It wastes your time and annoys the pig."}, + [119] = {name = "Free Items!", grade = 1, points = 3, secret = true, description = "Yay! Finders keepers, losers weepers! Who cares where all that stuff came from and if you had to crawl through garbage piles to get it? It's FREE!"}, + [120] = {name = "Godslayer", grade = 2, points = 4, description = "You have defeated the Snake God's incarnations and, with a final powerful swing of the snake sceptre, cut off his life force supply. The story of power, deceit and corruption has come to an end - or... not?"}, + [121] = {name = "Gold Digger", grade = 2, points = 4, secret = true, description = "Hidden treasures below the sand dunes of the desert - you have a nose for finding them and you know where to dig. They might not make you filthy rich, but they're shiny and pretty anyhow."}, + [122] = {name = "Happy Farmer", grade = 1, points = 1, secret = true, description = "Scythe swung over your shoulder, sun burning down on your back - you are a farmer at heart and love working in the fields. Or then again maybe you just create fancy crop circles to scare your fellow men."}, + [123] = {name = "Heartbreaker", grade = 1, points = 1, secret = true, description = "Trust? Love? Faithfulness? Pah! Antiquated sentiments. As long as you have fun, you do not mind stepping on lots of hearts. Preferably while wearing combat boots."}, + [124] = {name = "Homebrewed", grade = 1, points = 1, secret = true, description = "Yo-ho-ho and a bottle of rum - homebrewed, of course, made from handpicked and personally harvested sugar cane plants. Now, let it age in an oak barrel and enjoy it in about 10 years. Or for the impatient ones: Let's have a paaaarty right now!"}, + [125] = {name = "Hunting with Style", grade = 2, points = 6, description = "At daytime you can be found camouflaged in the woods laying traps or chasing big game, at night you're sitting by the campfire and sharing your hunting stories. You eat what you hunted and wear what you skinned. Life could go on like that forever."}, + [126] = {name = "I Need a Hug", grade = 1, points = 2, description = "You and your stuffed furry friends are inseparable, and you're not ashamed to take them to bed with you - who knows when you will wake up in the middle of the night in dire need of a cuddle?"}, + [127] = {name = "In Shining Armor", grade = 2, points = 6, description = "With edged blade and fully equipped in a sturdy full plate armor, you charge at your enemies with both strength and valour. There's always a maiden to save and a dragon to slay for you."}, + [128] = {name = "Joke's on You", grade = 1, points = 1, secret = true, description = "Well - the contents of that present weren't quite what you expected. With friends like these, who needs enemies?"}, + [129] = {name = "Keeper of the Flame", grade = 1, points = 2, secret = true, description = "One of the Lightbearers. One of those who helped to keep the basins burning and worked together against the darkness. The demonic whispers behind the thin veil between the worlds - they were silenced again thanks to your help."}, + [130] = {name = "Let the Sunshine In", grade = 1, points = 1, secret = true, description = "Rise and shine! It's a beautiful new day - open your windows, feel the warm sunlight, watch the birds singing on your windowsill and care for your plants. What reason is there not to be happy?"}, + [131] = {name = "Life on the Streets", grade = 2, points = 4, description = "You're a beggar, homeless, wearing filthy and ragged clothes. But that doesn't mean you have to beg anyone for stuff - and you still kept your pride. Fine feathers do not necessarily make fine birds - what's under them is more important."}, + [132] = {name = "Make a Wish", grade = 1, points = 1, secret = true, description = "But close your eyes and don't tell anyone what you wished for, or it won't come true!"}, + [133] = {name = "Master of War", grade = 2, points = 6, description = "You're not afraid to show your colours in the heat of battle. Enemies fear your lethal lance and impenetrable armor. The list of the wars you've won is impressive. Hail and kill!"}, + [134] = {name = "Mastermind", grade = 1, points = 3, description = "You feel you could solve the hardest riddles within a minute or so. Plus, there's a nice boost on your spell damage. All in a little bottle. Aftereffects - feeling slightly stupid. For further questions consult your healer or potion dealer."}, + [135] = {name = "Mister Sandman", grade = 1, points = 2, secret = true, description = "Tired... so tired... curling up in a warm and cosy bed seems like the perfect thing to do right now. Sweet dreams!"}, + [136] = {name = "Modest Guest", grade = 1, points = 1, secret = true, description = "You don't need much to sleep comfortably. A pile of straw and a roof over your head - with the latter being completely optional - is quite enough to relax. You don't even mind the rats nibbling on your toes."}, + [137] = {name = "Mutated Presents", grade = 1, points = 1, secret = true, description = "Muahahaha it's a... mutated pumpkin! After helping to take it down - you DID help, didn't you? - you claimed your reward and got a more or less weird present. Happy Halloween!"}, + [138] = {name = "Natural Sweetener", grade = 1, points = 1, secret = true, description = "Liberty Bay is the perfect hangout for you and harvesting sugar cane quite a relaxing leisure activity. Would you like some tea with your sugar, hon?"}, + [139] = {name = "Nightmare Walker", grade = 2, points = 6, description = "You do not fear nightmares, you travel in them - facing countless horrors and fighting the fate they're about to bring. Few believe the dark prophecies you bring back from those dreams, but those who do fight alongside you as Nightmare Knights."}, + [140] = {name = "Nothing Can Stop Me", grade = 1, points = 1, secret = true, description = "You laugh at unprepared adventurers stuck in high grass or rush wood. Or maybe you actually do help them out. They call you... 'Machete'."}, + [141] = {name = "Number of the Beast", grade = 1, points = 2, description = "Six. Six. Six."}, + [142] = {name = "Of Wolves and Bears", grade = 2, points = 6, description = "One with nature, one with wildlife. Raw and animalistic power, sharpened senses, howling on the highest cliffs and roaring in the thickest forests - that's you."}, + [143] = {name = "One Thousand and One", grade = 2, points = 6, description = "You feel at home under the hot desert sun with sand between your toes, and your favourite means of travel is a flying carpet. Also, you can probably do that head isolation dance move."}, + [144] = {name = "Oops", grade = 1, points = 2, secret = true, description = "So much for your feathered little friend! Maybe standing in front of the birdcage, squeezing its neck and shouting 'Sing! Sing! Sing!' was a little too much for it?!"}, + [145] = {name = "Out in the Snowstorm", grade = 2, points = 4, description = "Snow heaps and hailstorms can't keep you from where you want to go. You're perfectly equipped for any expedition into the perpetual ice and know how to keep your feet warm. If you're a woman, that's quite an accomplishment, too."}, + [146] = {name = "Peazzekeeper", grade = 2, points = 6, description = "You're a humble warrior who doesn't need wealth or specialised equipment for travelling and fighting. You feel at home in the northern lands of Zao and did your part in fighting its corruption."}, + [147] = {name = "Piece of Cake", grade = 1, points = 1, description = "Life can be so easy with the right cake at the right time - and you mastered baking many different ones, so you should be prepared for almost everything life decides to throw at you."}, + [148] = {name = "Ritualist", grade = 2, points = 6, description = "You could be the author of the magnum opus 'How to Summon the Ultimate Beast from the Infernal Depths, Volume I'. Or, if your mind and heart are pure, you rather summon beings to help others. Or maybe just a little cat to have someone to cuddle."}, + [149] = {name = "Rock Me to Sleep", grade = 1, points = 1, secret = true, description = "Sleeping - you do it with style. You're chilling in your hammock, listening to the sound of the birds and crickets as you slowly drift away into the realm of dreams."}, + [150] = {name = "Rocket in Pocket", grade = 1, points = 1, secret = true, description = "Either you are not a fast learner or you find some pleasure in setting yourself on fire. Or you're just looking for a fancy title. In any case, you should know that passing gas during your little donkey experiments is not recommended."}, + [151] = {name = "Rollercoaster", grade = 1, points = 1, description = "Up and down and up and down... and then the big looping! Wait - they don't build loopings in Kazordoon. But ore wagon rides are still fun!"}, + [152] = {name = "Santa's Li'l Helper", grade = 1, points = 2, secret = true, description = "Christmas is your favourite time of the year, and boy, do you love presents. Buy some nice things for your friends, hide them away until - well, until you decide to actually unwrap them rather yourself."}, + [153] = {name = "Sharpshooter", grade = 1, points = 3, description = "Improved eyesight, arrows and bolts flying at the speed of light and pinning your enemies with extra damage. All in a little bottle. No consumption of carrots required. For further questions consult your healer or potion dealer."}, + [154] = {name = "Skull and Bones", grade = 2, points = 6, description = "Wearing the insignia and dark robes of the Brotherhood of Bones you roam the lands spreading fear and pain, creating new soldiers for the necromantic army which is about to rise soon. Hail the Brotherhood."}, + [155] = {name = "Slim Chance", grade = 1, points = 1, description = "Okay, let's face it - as long as you believe it could potentially lead you to the biggest treasure ever, you won't let go of that map, however fishy it might look. There must be a secret behind all of this!"}, + [156] = {name = "Swashbuckler", grade = 2, points = 6, description = "Ye be a gentleman o' fortune, fightin' and carousin' on the high seas, out fer booty and lassies! Ye no be answerin' to no man or blasted monarchy and yer life ain't fer the lily-livered. Aye, matey!"}, + [157] = {name = "Sweet Tooth", grade = 1, points = 2, secret = true, description = "The famous 'Ode to a Molten Chocolate Cake' was probably written by you. Spending a rainy afternoon in front of the chimney, wrapped in a blanket while indulging in cocoa delights sounds just like something you'd do. Enjoy!"}, + [158] = {name = "Swift Death", grade = 2, points = 6, description = "Stealth kills and backstabbing are you specialty. Your numerous victims are usually unaware of their imminent death, which you bring to them silently and swiftly. Everything is permitted."}, + [159] = {name = "The Cake's the Truth", grade = 1, points = 1, secret = true, description = "And anyone claiming otherwise is a liar."}, + [160] = {name = "The Day After", grade = 1, points = 2, secret = true, description = "Uhm... who's that person who you just woke up beside? Broken cocktail glasses on the floor, flowers all over the room, and why the heck are you wearing a ring? Yesterday must have been a long, weird day..."}, + [161] = {name = "The Undertaker", grade = 1, points = 2, secret = true, description = "You and your shovel - a match made in heaven. Or hell, for that matter. Somewhere down below in any case. You're magically attracted by stone piles and love to open them up and see where those holes lead you. Good biceps as well."}, + [162] = {name = "True Lightbearer", grade = 2, points = 5, secret = true, description = "You're one of the most dedicated Lightbearers - without you, the demons would have torn the veil between the worlds for sure. You've lit each and every basin, travelling high and low, pushing back the otherworldly forces. Let there be light!"}, + [163] = {name = "Warlock", grade = 2, points = 6, description = "You're proficient in the darker ways of magic and are usually found sitting inside a circle of candles and skulls muttering unspeakable words. Don't carry things too far or the demons might come get you."}, + [164] = {name = "Way of the Shaman", grade = 2, points = 6, description = "Shaking your rattle and dancing around the fire to jungle drums sounds like something you like doing. Besides, dreadlocks are a convenient way to wear your hair - no combing required!"}, + [165] = {name = "Wild Warrior", grade = 2, points = 6, description = "Valour is for weaklings - it doesn't matter how you win the battle, as long as you're victorious. Thick armor would just hinder your movements, thus you keep it light and rely on speed and skill instead of hiding in an uncomfortable shell."}, + [166] = {name = "With a Cherry on Top", grade = 1, points = 1, secret = true, description = "You like your cake soft, with fruity bits and a nice sugar icing. And you prefer to make them by yourself. Have you ever considered opening a bakery? You must be really good by now!"}, + [167] = {name = "Yalahari of Power", grade = 1, points = 3, description = "You defend Yalahar with brute force and are ready to lead it into a glorious battle, if necessary. Thanks to you, Yalahar will be powerful enough to stand up against any enemy."}, + [168] = {name = "Yalahari of Wisdom", grade = 1, points = 3, description = "Your deeds for Yalahar are usually characterised by deep insight and thoughtful actions. Thanks to you, Yalahar might have a chance to grow peacefully and with happy people living in it."}, + + -- 8.7 + [169] = {name = "Afraid of no Ghost!", grade = 1, points = 2, description = "You passed their test and helped the Spirithunters testing equipment, researching the supernatural and catching ghosts - it's you they're gonna call."}, + [170] = {name = "Ashes to Dust", grade = 2, points = 4, secret = true, description = "Staking vampires and demons has almost turned into your profession. You make sure to gather even the tiniest amount of evil dust particles. Beware of silicosis."}, + [171] = {name = "Baby Sitter", grade = 1, points = 1, secret = true, description = "You have cheered up a demon baby and returned it to its mother. A quick count of your fingers will reveal if you made it through unharmed."}, + [172] = {name = "Banebringers' Bane", grade = 1, points = 2, secret = true, description = "You sacrificed a lot of ingredients to create the protective brew of the witches and played a significant part in the efforts to repel the dreaded banebringers. The drawback is that even the banebringers may take notice of you ..."}, + [173] = {name = "Berry Picker", grade = 2, points = 4, secret = true, description = "The Combined Magical Winterberry Society hereby honours continued selfless dedication and extraordinary efforts in the Annual Autumn Vintage."}, + [174] = {name = "Bunny Slipped", grade = 1, points = 2, description = "Indeed, you have a soft spot for rabbits. Maybe the rabbits you saved today will be the rabbits that will save you tomorrow. When you are really hungry."}, + [175] = {name = "Cake Conqueror", grade = 1, points = 1, description = "You have bravely stepped onto the cake isle. Is there any more beautiful, tasty place to be in the whole world?"}, + [176] = {name = "Dark Voodoo Priest", grade = 1, points = 2, secret = true, description = "Sinister curses, evil magic - you don't shy away from punishing others by questionable means. Someone just gave you a strange look - now where's that needle again?"}, + [177] = {name = "Extreme Degustation", grade = 1, points = 2, secret = true, description = "Almost all the plants you tested for Chartan in Zao where inedible - you tasted them all, yet you're still standing! You should really get some fresh air now, though."}, + [178] = {name = "Fire Devil", grade = 1, points = 3, secret = true, description = "To keep the witches' fire burning, you trashed a lot of the wood the bane bringers animated. Some might find your fascination for fire ... disturbing."}, + [179] = {name = "Fire Lighter", grade = 1, points = 1, secret = true, description = "You have helped to keep the witches fire burning. Just watch your fingers, it's hot!"}, + [180] = {name = "Ghost Sailor", grade = 1, points = 1, secret = true, description = "You have sailed the nether seas with the Ghost Captain. Despite the perils, you and your fellow crewmen have braved the challenge."}, + [181] = {name = "Guinea Pig", grade = 1, points = 2, description = "True scientists know their equipment. Testing new inventions is essential daily work for any hard working researcher. You showed no fear and took all the new equipment from Spectulus and Sinclair for a spin."}, + [182] = {name = "Hidden Powers", grade = 1, points = 2, description = "You've discovered the Ancients' hidden powers - from now on, they will aid you in your adventures."}, + [183] = {name = "Honorary Witch", grade = 2, points = 4, secret = true, description = "Your efforts in fighting back the banebringers has not gone unnoticed. You are a legend amongst the witches and your name is whispered with awe and admiration."}, + [184] = {name = "I Like it Fancy", grade = 1, points = 1, secret = true, description = "You definitely know how to bring out the best in your furniture and decoration pieces. Beautiful."}, + [185] = {name = "Master Shapeshifter", grade = 1, points = 2, secret = true, description = "You have mastered Kuriks challenge in all possible shapes."}, + [186] = {name = "Merry Adventures", grade = 1, points = 2, description = "You went into the forest, met Rottin Wood and the Married Men and helped them out in their camp. Oh, and don't worry about those merchants. They won't dare mentioning the strangely large sums of gold they actually possessed which are missing now."}, + [187] = {name = "Nanny from Hell", grade = 1, points = 3, secret = true, description = "You have cheered up a bunch of demon babies and returned them to their mother. Don't bother the burn marks, don't bother the strains of grey hair, don't bother the nights you wake up screaming. It was worth it ... probably ... somehow."}, + [188] = {name = "Natural Born Cowboy", grade = 1, points = 1, secret = true, description = "Oh, the joy of riding! You've just got your very first own mount. Conveniently enough you don't even need stables, but can summon it any time you like."}, + [189] = {name = "Nether Pirate", grade = 1, points = 3, secret = true, description = "Not fearing death or ghosts you have traveled with the ghost captain several times and are a seasoned traveler of the netherworld. The dead and the living whisper about your exploits with appreciation."}, + [190] = {name = "Nomad Soul", grade = 1, points = 2, secret = true, description = "Home is where your current favourite hunting ground is, and though you might hold certain places more dear than others you never feel attached enough to really stay in one city for long. Pack all your stuff - it's time to move on again."}, + [191] = {name = "Petrologist", grade = 1, points = 2, secret = true, description = "Stones have always fascinated you. So has the chance of finding something really precious inside one of them. Statistically you should've discovered a few nice treasures by now. But then again, most statistics are overriden by Mother Disfortune."}, + [192] = {name = "Pyromaniac", grade = 2, points = 4, secret = true, description = "Love ... fire! So ... shiny! Must ... buuuurrrn!"}, + [193] = {name = "Safely Stored Away", grade = 1, points = 2, secret = true, description = "Don't worry, no one will be able to take it from you. Probably."}, + [194] = {name = "Scourge of Death", grade = 2, points = 5, secret = true, description = "You are a master of the nether sea and have traveled with the ghost captain so many times that you know his ship and the perils of the nether sea inside out. You laugh in the face of death and may return as a ghost pirate yourself in the afterlife!"}, + [195] = {name = "Silent Pet", grade = 1, points = 1, secret = true, description = "Awww. Your very own little goldfish friend - he's cute, he's shiny and he can't complain should you forget to feed him. He'll definitely brighten up your day!"}, + [196] = {name = "Skin-Deep", grade = 2, points = 4, secret = true, description = "You always carry your obsidian knife with you and won't hesitate to use it. You've skinned countless little - and bigger - critters and yeah: they usually don't get any more beautiful on the inside. It's rather blood and gore and all that..."}, + [197] = {name = "Snowbunny", grade = 1, points = 2, secret = true, description = "Hopping, hopping through the snow - that's the funnest way to go! Making footprints in a flurry - it's more fun the more you hurry! Licking icicles all day - Winter, never go away!"}, + [198] = {name = "Something's in There", grade = 1, points = 1, secret = true, description = "By the gods! What was that?"}, + [199] = {name = "Spectral Traveler", grade = 1, points = 2, secret = true, description = "You have sailed the nether seas with the Ghost Captain several times. The dangers of the nether have become familiar to you and unexperienced travelers turn to you for advice."}, + [200] = {name = "True Colours", grade = 1, points = 3, secret = true, description = "You and your friends showed the three wizards your loyalty three times - I am sure at least one of them is probably eternally thankful and exceedingly proud of you."}, + [201] = {name = "Truth Be Told", grade = 1, points = 2, secret = true, description = "You told Jack the truth by explaining you and Spectulus made a mistake when trying to convince him of being a completely different person."}, + [202] = {name = "Witches Lil' Helper", grade = 1, points = 1, secret = true, description = "You sacrificed ingredients to create the protective brew of the witches."}, + [203] = {name = "You Don't Know Jack", grade = 1, points = 2, secret = true, description = "You did not tell Jack the truth about the mistake you and Spectulus made when trying to convince him about being a completely different person. He will live in doubt until the end of his existence."}, + + -- 9.1 + [204] = {name = "Askarak Nemesis", grade = 1, points = 1, secret = true, description = "You are now the royal archfiend of the Askarak, prince slayer."}, + [205] = {name = "Beak Doctor", grade = 2, points = 4, description = "You significantly helped the afflicted citizens of Venore in times of dire need. Somehow you still feel close to the victims of the fever outbreak. Your clothes make you one of them, one poor soul amongst the countless afflicted."}, + [206] = {name = "Biodegradable", grade = 1, points = 1, secret = true, description = "You caught fifty rare shimmer swimmers. Getting rid of all those corpses by dumping them into the lake really was worth it, wasn't it? Wait, didn't something move in the water just now...?"}, + [207] = {name = "Deer Hunt", grade = 1, points = 1, secret = true, description = "You managed to kill more than four hundred white deer - it looks like you are one of the main reasons they will soon be considered extinct, way to go!"}, + [208] = {name = "Doctor! Doctor!", grade = 1, points = 2, secret = true, description = "Did someone call a doctor? You delivered 100 medicine bags to Ottokar of the Venore poor house in times of dire need, well done!"}, + [209] = {name = "Eye of the Deep", grade = 1, points = 1, secret = true, description = "You didn't look into it - at least not for too long... but Groam did. And you relieved him. Just don't tell his friend Dronk."}, + [210] = {name = "Firefighter", grade = 1, points = 2, secret = true, description = "You extinguished 500 thornfires! You were there when the Firestarters took over Shadowthorn. You saved the day - and the home of some elves which will try to kill you nonetheless. Isn't it nice to see everything restored just as it was before..?"}, + [211] = {name = "Invader of the Deep", grade = 1, points = 2, secret = true, description = "Many creatures of the deep have lost their lives by your hand. Three hundred have entered the depths of eternity. You should probably fear the revenge of the Eyes of the Deep."}, + [212] = {name = "Mageslayer", grade = 1, points = 1, secret = true, description = "You killed the raging mage in his tower south of Zao. Again. But this one just keeps coming back. The dimensional portal collapsed once more and you know he will eventually return but hey - a raging mage, it's like asking for it..."}, + [213] = {name = "Mystic Fabric Magic", grade = 2, points = 4, description = "You vanquished the mad mage, you subdued the raging mage - no spellweaving self-exposer can stand in your way. Yet you are quite absorbed in magical studies yourself. This very fabric reflects this personal approval of the magic arts."}, + [214] = {name = "Shaburak Nemesis", grade = 1, points = 1, secret = true, description = "You are now the public archenemy of the Shaburak, prince slayer."}, + [215] = {name = "Slimer", grade = 1, points = 1, secret = true, description = "With the assistance of your friendly little helper, you gobbled more than 500 chunks of slime. Well done, Slimer."}, + + -- 9.2 + [216] = {name = "Arachnoise", grade = 1, points = 1, description = "You've shattered each of Bloodweb's eight frozen legs. As they say: break a leg, and then some more."}, + [217] = {name = "Back into the Abyss", grade = 1, points = 1, description = "You've cut off a whole lot of tentacles today. Thul was driven back to where he belongs."}, + [218] = {name = "Beautiful Agony", grade = 1, points = 2, description = "Ethershreck's cry of agony kept ringing in your ear for hours after he had dissolved into thin air. He probably moved to another plane of existence... for a while."}, + [219] = {name = "Blood-Red Snapper", grade = 1, points = 1, description = "You've tainted the jungle floor with the Snapper's crimson blood."}, + [220] = {name = "Breaking the Ice", grade = 1, points = 1, description = "You almost made friends with Shardhead... before he died. Poor guy only seems to attract violence with his frosty attitude."}, + [221] = {name = "Choking on Her Venom", grade = 1, points = 1, description = "The Old Widow fell prey to your supreme hunting skills."}, + [222] = {name = "Crawling Death", grade = 1, points = 1, description = "You ripped the ancient scarab Fleshcrawler apart and made sure he didn't get under your skin."}, + [223] = {name = "Hissing Downfall", grade = 1, points = 2, description = "You've vansquished the Noxious Spawn and his serpentine heart."}, + [224] = {name = "Just Cracked Me Up!", grade = 1, points = 2, description = "Stonecracker's head was much softer than the stones he threw at you."}, + [225] = {name = "Meat Skewer", grade = 1, points = 1, description = "You've impaled the big mammoth Bloodtusk with his own tusks."}, + [226] = {name = "No More Hiding", grade = 1, points = 1, description = "You've found a well-hidden spider queen and caught her off guard in the middle of her meal."}, + [227] = {name = "One Less", grade = 1, points = 2, description = "The Many is no more, but how many more are there? One can never know."}, + [228] = {name = "Pwned a Lot of Fur", grade = 3, points = 8, secret = true, description = "You've faced and defeated a lot of the mighty bosses the Paw and Fur society sent you out to kill. All by yourself. What a hunt!"}, + [229] = {name = "Rootless Behaviour", grade = 1, points = 1, description = "You've descended into the swampy depths of Deathbine's lair and made quick work of it."}, + [230] = {name = "Scorched Flames", grade = 1, points = 1, description = "A mighty blaze went out today. It's Flameborn's turn to wait for his rebirth in the eternal cycle of life and death."}, + [231] = {name = "Something Smells", grade = 1, points = 1, description = "You've exinguished the Sulphur Scuttler's gas clouds and made the air in his cave a little better... at least for a while."}, + [232] = {name = "Spareribs for Dinner", grade = 1, points = 1, description = "Ribstride is striding no more. He had quite a few ribs to spare though."}, + [233] = {name = "The Drowned Sea God", grade = 1, points = 2, description = "As the killer of Leviathan, the giant sea serpent, his underwater kingdom is now under your reign."}, + [234] = {name = "The Gates of Hell", grade = 1, points = 2, description = "It seems the gates to the underworld have to remain unprotected for a while. Kerberos, the mighty hellhound, lost his head. All three of them."}, + [235] = {name = "The Serpent's Bride", grade = 1, points = 2, description = "You made a knot with Gorgo's living curls and took her scalp. You couldn't save her countless petrified victims, but at least you didn't become one."}, + [236] = {name = "Twisted Mutation", grade = 1, points = 1, description = "You've slain Esmeralda, the most hideous and aggressive of the mutated rats. No one will know that you almost lost a finger in the process."}, + + -- 9.4 + [237] = {name = "Bane of the Hive", grade = 1, points = 2, description = "Countless fights and never tiring effort in the war against the hive grant you the experience to finish your outfit with the last remaining part. Your chitin outfit is a testament of your skills and dedication for the cause."}, + [238] = {name = "Chest Robber", grade = 1, points = 1, description = "You've discovered three nomad camps and stole their supplies. Well, you can probably use them better then they can."}, + [239] = {name = "Chitin Bane", grade = 2, points = 4, description = "You have become competent and efficient in gathering the substance that is needed to fight the hive. You almost smell like dissolved chitin and the Hive Born would tell their children scary stories about you if they could speak."}, + [240] = {name = "Confusion", grade = 1, points = 3, description = "The destruction you have caused by now can be felt throughout the whole hive. The mayhem that follows your step caused significant confusion in the consciousness of the hive."}, + [241] = {name = "Dazzler", grade = 1, points = 3, description = "In the war against the hive, your efforts in blinding it begin to pay off. Your actions have blinded the hive severely and the entity seems to become aware that something dangerous is happening."}, + [242] = {name = "Death Song", grade = 1, points = 3, description = "You hushed the songs of war in the black depths by sliencing more than three hundred Deepling Spellsingers."}, + [243] = {name = "Depth Dwellers", grade = 1, points = 3, description = "By eliminating at least three hundred Deepling Warriors you delivered quite a blow to the amassing armies of the deep."}, + [244] = {name = "Desert Fisher", grade = 1, points = 1, description = "You managed to catch a fish in a surrounding that usually doesn't even carry water. Everything is subject to change, probably..."}, + [245] = {name = "Dog Sitter", grade = 1, points = 1, description = "You showed Noodles the way home. How long will it take this time until he's on the loose again? That dog must be really bored in the throne room by now."}, + [246] = {name = "Down the Drain", grade = 1, points = 2, description = "You've found a secret dungeon in the flooded plains and killed several of its inhabitants. And now you have wet feet."}, + [247] = {name = "Exterminator", grade = 2, points = 4, description = "Efficient and lethal, you have gained significant experience in fighting the elite forces of the hive. Almost single-handed, you have slain the best of the Hive Born and live to tell the tale."}, + [248] = {name = "Fire from the Earth", grade = 1, points = 2, description = "You've survived the Hellgorge eruption and found a way through the flames and lava. You've even managed to kill a few fireborn on the way."}, + [249] = {name = "Gatherer", grade = 1, points = 2, description = "By killing creatures of the hive and gaining weapons for further missions, you started a quite effective way of war. You gathered a lot of dissolved chitin to resupply the war effort."}, + [250] = {name = "Gem Cutter", grade = 1, points = 1, secret = true, description = "You cut your first gem - and it bears your own name! Now that would be a nice gift! This does not make it a \"true\" Heart of the Sea, however..."}, + [251] = {name = "Goldhunter", grade = 1, points = 2, secret = true, description = " If it wasn't for you, several banks in the World would've gotten bankrupt by now. Keep on chasing bank robbers and no one will have to worry about the World economy!"}, + [252] = {name = "Guard Killer", grade = 1, points = 2, description = "You have proven that you can beat the best of the hive. You have caused first promising breaches in the defence of the hive"}, + [253] = {name = "Guardian Downfall", grade = 2, points = 4, description = "You ended the life of over three hundred Deepling Guards. Not quite the guardian of the Deeplings, are you?"}, + [254] = {name = "Headache", grade = 1, points = 2, description = "Even in the deepest structures of the hive, you began to strike against the mighty foe. Your actions probably already gave the hive a headache."}, + [255] = {name = "Heartburn", grade = 1, points = 3, description = "Never-tiring, you attack the inner organs of the mighty hive. Your attacks on the hive's digestion system begin to cause some trouble."}, + [256] = {name = "Hickup", grade = 1, points = 2, description = "You have grown accustomed to frequenting the hive's stomach system. Your actions have caused the hive some first digestion problems."}, + [257] = {name = "Hive Blinder", grade = 2, points = 4, description = "You have put a lot of time and energy into keeping the hive unaware of what is happening on Quirefang. The hive learnt to fear your actions. It would surely crush you with all its might ... if it could only find you!"}, + [258] = {name = "Hive Fighter", grade = 1, points = 1, description = "You have participated that much in the hive war, that you are able to create some makeshift armor from the remains of dead hive born that can be found in the major hive, to show of your skill."}, + [259] = {name = "Hive Infiltrator", grade = 1, points = 3, description = "The most powerful warriors of the hive were killed by you by the dozens. The hive is not safe anymore because of your actions."}, + [260] = {name = "Hive War Veteran", grade = 1, points = 1, description = "Your invaluable experience in fighting the hive allows you to add another piece of armor to your chitin outfit to proove your dedication for the cause."}, + [261] = {name = "Honest Finder", grade = 1, points = 1, description = "You've stopped the bank robber and returned the bag full of gold. Good to know there are still lawful citizens like you around."}, + [262] = {name = "Ice Harvester", grade = 1, points = 1, description = "You witnessed the thawing of Svargrond and harvested rare seeds from some strange icy plants. They must be good for something."}, + [263] = {name = "Loyal Subject", grade = 1, points = 1, description = "You joined the Kingsday festivities and payed the King your respects. Now, off to party!"}, + [264] = {name = "Manic", grade = 2, points = 4, description = "You have destroyed a significant amount of the hive's vital nerve centres and caused massive destruction to the hive's awareness. You are probably causing the hive horrible nightmares."}, + [265] = {name = "Minor Disturbance", grade = 1, points = 2, description = "Your actions start to make a difference. You have blinded the antennae of the hive often enough to become an annoyance to it."}, + [266] = {name = "Navigational Error", grade = 2, points = 5, secret = true, description = "You confronted the Navigator."}, + [267] = {name = "Pimple", grade = 1, points = 3, description = "You are getting more and more experienced in destroying the supply of the enemy's forces. Your actions caused the hive some severe skin problems."}, + [268] = {name = "Planter", grade = 1, points = 2, description = "The hive has to be fought with might and main, hampering its soldiers is only the first step. You diligently stopped the pores of the hive to spread its warriors."}, + [269] = {name = "Preservationist", grade = 1, points = 1, secret = true, description = "You are a pretty smart thinker and managed to create everlasting flowers. They might become a big hit with all the people who aren't blessed with a green thumb or just forgetful."}, + [270] = {name = "Si, Ariki!", grade = 1, points = 1, description = "You've found the oriental traveller Yasir and were able to trade with him - even if you didn't really understand his language."}, + [271] = {name = "Someone's Bored", grade = 1, points = 1, secret = true, description = "That was NOT a giant spider. There's some witchcraft at work here."}, + [272] = {name = "Spolium Profundis", grade = 2, points = 4, description = "You travelled the depths of this very world. You entered the blackness of the deep sea to conquer the realm of the Deeplings. May this suit remind you of the strange beauty below."}, + [273] = {name = "Stomach Ulcer", grade = 2, points = 4, description = "You severely disrupted the digestion of the hive. The hive should for sure see a doctor. It seems you proved to be more than it can swallow."}, + [274] = {name = "Supplier", grade = 1, points = 3, description = "The need for supplies often decides over loss or victory. Your tireless efforts to resupply the resources keeps the war against the hive going."}, + [275] = {name = "Suppressor", grade = 2, points = 4, description = "A war is won by those who have the best supply of troops. The hive's troops have been dealt a significant blow by your actions. You interrupted the hive's replenishment of troops lastingly and severely."}, + [276] = {name = "Torn Treasures", grade = 1, points = 1, secret = true, description = "Wyda seems to be really, really bored. You also found out that she doesn't really need all those blood herbs that adventurers brought her. Still, she was nice enough to take one from you and gave you something quite cool in exchange."}, + [277] = {name = "Trail of the Ape God", grade = 1, points = 1, secret = true, description = "You've discovered a trail of giant footprints and Terrified Elephants running everywhere. Could it be that the mysterious Ape God is rambling in the jungle?"}, + [278] = {name = "Whistle-Blower", grade = 1, points = 1, secret = true, description = "You can't keep a secret, can you? Then again, you're just fulfilling your duty to the Queen of Carlin as a lawful citizen. That's a good thing, isn't it...?"}, + + -- 9.5 + [279] = {name = "Back from the Dead", grade = 1, points = 2, description = "You overcame the undead Zanakeph and sent him back into the darkness that spawned him."}, + [280] = {name = "Dream's Over", grade = 1, points = 1, description = "No more fear and bad dreams. You stabbed Tormentor to death with its scythe leg."}, + [281] = {name = "Enter zze Draken!", grade = 1, points = 2, description = "You gave zzze draken a tazte of your finizzzing move."}, + [282] = {name = "Howly Silence", grade = 1, points = 1, description = "You muted the everlasting howling of Hemming."}, + [283] = {name = "Kapow!", grade = 1, points = 1, description = "No joke, you murdered the bat."}, + [284] = {name = "King of the Ring", grade = 1, points = 2, description = "Bretzecutioner's body just got slammed away. You are a true king of the ring!"}, + [285] = {name = "Pwned All Fur", grade = 3, points = 8, secret = true, description = "You've faced and defeated each of the mighty bosses the Paw and Fur society sent you out to kill. All by yourself. What a hunt!"}, + [286] = {name = "Stepped on a Big Toe", grade = 1, points = 1, description = "This time you knocked out the big one."}, + [287] = {name = "Zzztill Zzztanding!", grade = 1, points = 1, description = "You wiped Fazzrah away - zzeemzz like now you're the captain."}, + + -- 9.6 + [288] = {name = "Becoming a Bigfoot", grade = 1, points = 1, description = "You did it! You convinced the reclusive gnomes to accept you as one of their Bigfoots. Now you are ready to help them. With big feet big missions seen to come."}, + [289] = {name = "Bibby's Bloodbath", grade = 1, points = 1, secret = true, description = "You lend a helping hand in defeating invading Orcs by destroying their warcamp along with their leader. Bibby's personal bloodbath..."}, + [290] = {name = "Call Me Sparky", grade = 1, points = 1, description = "Admittedly you enjoyed the killing as usual. But the part with the sparks still gives you shivers ... or is it that there is some charge left on you?"}, + [291] = {name = "Crystal Clear", grade = 1, points = 3, description = "If the gnomes had told you that crystal armor is see-through you had probably changed your underwear in time."}, + [292] = {name = "Crystal Keeper", grade = 1, points = 1, description = "So you repaired the light of some crystals for those gnomes. What's next? Sitting a week in a mushroom bed as a temporary mushroom?"}, + [293] = {name = "Crystals in Love", grade = 1, points = 1, description = "You brought two loving crystals together. Perhaps they might even name one of their children after you. To bad you forgot to leave your calling card."}, + [294] = {name = "Death from Below", grade = 1, points = 2, secret = true, description = "The face of the enemy is unmasked. You have encountered one of 'those below' and survived. More than that, you managed to kill the beast and prove once and for all that the enemy can be beaten."}, + [295] = {name = "Death on Strike", grade = 2, points = 4, secret = true, description = "Again and again Deathstrike has fallen to your prowess. Perhaps it's time for people calling YOU Deathstrike from now on."}, + [296] = {name = "Diplomatic Immunity", grade = 2, points = 4, secret = true, description = "You killed the ambassador of the abyss that often that they might consider sending another one. Perhaps that will one day stop further intrusions."}, + [297] = {name = "Dungeon Cleaner", grade = 1, points = 3, secret = true, description = "Seen it all. Done it all. Your unstoppable force swept through the dungeons and you vanquished their masters. Not to forget the precious loot you took! Now stop reading this and continue hunting! Time is money after all!"}, + [298] = {name = "Fall of the Fallen", grade = 2, points = 4, secret = true, description = "Have you ever wondered how he reappears again and again? You only care for the loot, do you? Gotcha!"}, + [299] = {name = "Final Strike", grade = 1, points = 2, secret = true, description = "The mighty Deathstrike is dead! One legend is dead and you're on your way to become one yourself."}, + [300] = {name = "Funghitastic", grade = 1, points = 3, description = "Finally your dream to become a walking mushroom has come true ... No, wait a minute!"}, + [301] = {name = "Gnome Friend", grade = 1, points = 2, description = "The gnomes are warming up to you. One or two of them might actually bother to remember your name. You're allowed to access their gnomebase alpha. You are prepared to boldly put your gib feet into areas few humans have walked before."}, + [302] = {name = "Gnome Little Helper", grade = 1, points = 1, description = "You think the gnomes start to like you. A little step for a Bigfoot but a big step for humanity."}, + [303] = {name = "Gnomebane's Bane", grade = 1, points = 2, secret = true, description = "The fallen gnome is dead and justice served. But what was it that the gnome whispered with his last breath? He's your father???"}, + [304] = {name = "Gnomelike", grade = 1, points = 3, description = "You have become a household name in gnomish society! Your name is mentioned by gnomes more than once. Of course usually by gnomish mothers whose children refuse to eat their mushroom soup, but you are certainly making some tremendous progress."}, + [305] = {name = "Gnomish Art Of War", grade = 1, points = 3, description = "You have unleashed your inner gnome and slain some of the most fearsome threats that gnomekind has ever faced. Now you can come and go to the warzones as it pleases you. The enemies of gnomekind will never be safe again."}, + [306] = {name = "Goo Goo Dancer", grade = 1, points = 1, secret = true, description = "Seeing a mucus plug makes your heart dance and you can't resist to see what it hides. Goo goo away!"}, + [307] = {name = "Grinding Again", grade = 1, points = 1, description = "Burnt fingers and itching lungs are a small price for bringing those gnomes some lousy stone and getting almost killed! Your mother warned you to better become a farmer."}, + [308] = {name = "Honorary Gnome", grade = 2, points = 4, description = "You accomplished what few humans ever will: you truly impressed the gnomes. This might not change their outlook on humanity as a whole, but at least you can bathe in gnomish respect! And don't forget you're now allowed to enter the warzones!"}, + [309] = {name = "Nestling", grade = 1, points = 1, description = "You cleansed the land from an eight legged nuisance by defeating Mamma Longlegs three times. She won't be back soon... or will she?"}, + [310] = {name = "One Foot Vs. Many", grade = 1, points = 1, description = "One Bigfoot won over thousands of tiny feet. Perhaps the gnomes are wrong and size matters?"}, + [311] = {name = "Spore Hunter", grade = 1, points = 1, description = "After hunting for the correct mushrooms and their spores you're starting to feel like a mushroom yourself. A few times more and you might start thinking like a mushroom, who knows?"}, + [312] = {name = "Substitute Tinker", grade = 1, points = 1, description = "Ring-a-ding! You have visited the golem workshop and lent a hand in repairing them. To know those golems are safe is worth all the bruises, isn't it?"}, + [313] = {name = "The Picky Pig", grade = 1, points = 1, description = "The gnomes decided their pigs need some exclusive diet and you had to do all the dirty work - but wasn't the piglet adorable?"}, + + -- 9.8 + [314] = {name = "Task Manager", grade = 1, points = 2, secret = true, description = "Helping a poor, stupid goblin to feed his starving children and wifes feels good ... if you'd only get rid of the strange feeling that you're missing something."}, + [315] = {name = "True Dedication", grade = 2, points = 5, secret = true, description = "You conquered the demon challenge and prevailed... now show off your success in style!"}, + + -- 10.1 + [316] = {name = "Gravedigger", grade = 1, points = 3, description = "Assisting Omrabas' sick plan to resurrect made you dig your way through the blood-soaked halls of Drefia. Maybe better he failed!"}, + [317] = {name = "Repenter", grade = 1, points = 1, secret = true, description = "You cleansed your soul in serving the Repenter enclave and purified thine self in completing all tasks in a single day of labour."}, + + -- 10.2 + [318] = {name = "Cave Completionist", grade = 1, points = 2, description = " You have helped the gnomes of the spike in securing the caves and explored enough of the lightles depths to earn you a complete cave explorers outfit. Well done!"}, + + -- 10.3 + [319] = {name = "Dream Warden", grade = 2, points = 5, description = "It doesn't matter what noise you would hear... dream, nightmare, illusion - there is nothing you can't vanquish. You are a true Dream Warden."}, + [320] = {name = "Dream Wright", grade = 1, points = 1, description = "You have mended many a broken dream and so, the dream of Roshamuul is safely being told over and over again."}, + [321] = {name = "Ending the Horror", grade = 1, points = 2, description = "You have cleansed the lands of many retching horrors. You sure know how to end a bad dream: forcefully, that's how!"}, + [322] = {name = "Luring Silence", grade = 1, points = 2, description = "What a scientific discovery - they really DO communicate! Using their own communication habits against them, you lured a large pack of silencers away from the walls of Roshamuul."}, + [323] = {name = "Never Surrender", grade = 1, points = 3, description = "You did not show any signs of surrender to any sight of... you get the picture. Even a hundred of them did not pose a threat to you."}, + [324] = {name = "Nevermending Story", grade = 1, points = 3, secret = true, description = "You collected all of the mysterious bottle messages around the island of Roshamuul and located the remains of the first mate. Time will tell if his tale of mending an evil ring holds true."}, + [325] = {name = "Noblesse Obliterated", grade = 2, points = 6, description = "After a battle like this you know who your friends are."}, + [326] = {name = "Prison Break", grade = 3, points = 8, description = "Gaz'haragoth... a day to remember! Your world accomplished someting really big - and you have been part of it!"}, + [327] = {name = "Sleepwalking", grade = 1, points = 1, description = "You know your way, in dream and waking. And how to make tea that transcends the boundaries of conscience."}, + [328] = {name = "Umbral Archer", grade = 2, points = 6, description = "You managed to transform, improve and sacrify your bow into a master state and have proven yourself worthy in a nightmarish world."}, + [329] = {name = "Umbral Berserker", grade = 2, points = 6, description = "You managed to transform, improve and sacrify your hammer into a master state and have proven yourself worthy in a nightmarish world."}, + [330] = {name = "Umbral Bladelord", grade = 2, points = 6, description = "You managed to transform, improve and sacrify your slayer into a master state and have proven yourself worthy in a nightmarish world."}, + [331] = {name = "Umbral Brawler", grade = 2, points = 6, description = "You managed to transform, improve and sacrify your mace into a master state and have proven yourself worthy in a nightmarish world."}, + [332] = {name = "Umbral Executioner", grade = 2, points = 6, description = "You managed to transform, improve and sacrify your chopper into a master state and have proven yourself worthy in a nightmarish world."}, + [333] = {name = "Umbral Harbringer", grade = 2, points = 6, description = "You managed to transform, improve and sacrify your spellbook into a master state and have proven yourself worthy in a nightmarish world."}, + [334] = {name = "Umbral Headsman", grade = 2, points = 6, description = "You managed to transform, improve and sacrify your axe into a master state and have proven yourself worthy in a nightmarish world."}, + [335] = {name = "Umbral Marksman", grade = 2, points = 6, description = "You managed to transform, improve and sacrify your crossbow into a master state and have proven yourself worthy in a nightmarish world."}, + [336] = {name = "Umbral Master", grade = 3, points = 8, description = "You managed to transform, improve and sacrify all kinds of weapons into a master state and have proven yourself worthy in a nightmarish world. Respect!"}, + [337] = {name = "Umbral Swordsman", grade = 2, points = 6, description = "You managed to transform, improve and sacrify your blade into a master state and have proven yourself worthy in a nightmarish world."}, + + -- 10.5 + [338] = {name = "Combo Master", grade = 1, points = 1, secret = true, description = "You accomplished 10 or more consecutive chains in a row! That's killing at least 39 creatures in the correct order - now that's combinatorics!"}, + [339] = {name = "Elementary, My Dear", grade = 1, points = 1, description = "Through the spirit of science and exploration, you have discovered how to enter the secret hideout of the renowned Dr Merlay."}, + [340] = {name = "Glooth Engineer", grade = 2, points = 5, description = "Though you might have averted a dire threat for Rathleton, this relative peace may only hold for a while. At least you've scavenged an outfit from some of the poor fellows that have fallen prey to death priest Shagron."}, + [341] = {name = "Rathleton Citizen", grade = 1, points = 1, description = "By having rendered numerous services to the city of Rathleton you have been promoted to the rank of Citizen."}, + [342] = {name = "Rathleton Commoner", grade = 1, points = 1, description = "By having rendered numerous services to the city of Rathleton you have been promoted to the rank of Commoner."}, + [343] = {name = "Rathleton Inhabitant", grade = 1, points = 1, description = "By having rendered numerous services to the city of Rathleton you have been promoted to the rank of Inhabitant."}, + + -- 10.56 + [344] = {name = "Seasoned Adventurer", grade = 1, points = 1, description = "Adventure is your middle name. You spent much time in dangerous lands and have seen things others only dream of. You know your way around in the World - you are a seasoned adventurer now. And your journey has only just begun!"}, + + -- 10.7 + [345] = {name = "Go with da Lava Flow", grade = 1, points = 1, secret = true, description = "You escaped the glowing hot lava death trap, Professor Maxxen has set for you - Captain Caveworm is indeed proud!"}, + [346] = {name = "Lion's Den Explorer", grade = 1, points = 1, secret = true, description = "You discovered the Lion's Rock, passed the tests to enter the inner sanctum and finally revealed the secrets of the buried temple. You literally put your head in the lion's mouth and survived."}, + [347] = {name = "Mind the Step!", grade = 1, points = 1, description = "You've got a mind ready to draw strange conclusions that defy the laws of logic and sidestep reality. Or maybe it's just a lucky guess - or adventurous recklessness?"}, + [348] = {name = "Plant vs. Minos", grade = 1, points = 4, secret = true, description = "You have defeated the wallbreaker and saved the glooth plant."}, + [349] = {name = "Publicity", grade = 1, points = 1, description = "You are a man of the public. Or of good publicity at least. Through your efforts in advertising the airtight cloth, Zeronex might yet be redeemed - and Rathleton might yet see its first working Gloud Ship."}, + [350] = {name = "Rathleton Squire", grade = 1, points = 1, description = "By having rendered numerous services to the city of Rathleton you have been promoted to the rank of Squire."}, + [351] = {name = "Robo Chop", grade = 1, points = 4, secret = true, description = "You have defeated the glooth bomb and chopped down a lot of metal monsters on your way."}, + [352] = {name = "Rumble in the Plant", grade = 2, points = 4, secret = true, description = "You have defeated the tremor worm - and wonder what kind of fish you'd be able to catch with such a bait."}, + [353] = {name = "Snake Charmer", grade = 1, points = 1, description = "By restoring the Everhungry Altar, you charmed the Fire-Feathered Sea Serpent back into its fitful sleep, twenty miles beneath the sea."}, + [354] = {name = "The Professor's Nut", grade = 1, points = 3, description = "He seriously stored away a wallnut? That was a nutty professor indeed."}, + [355] = {name = "Wail of the Banshee", grade = 1, points = 1, secret = true, description = "You saw the Crystal Gardens with all their stunning beauty and survived the equally impressive monsters there. In the end you discovered a great evil and destroyed it with the help of a banshee who was not even aware of her support."}, + + -- 10.8 + [356] = {name = "Bearbaiting", grade = 1, points = 1, description = "Hunter's greeting! Your skillful use of the slingshot actually stunned a large bear. The creature is slightly dazed, but seems susceptible to your commands. Let's declare open season on all our foes!"}, + [357] = {name = "Beneath the Sea", grade = 1, points = 3, description = "Not really twenty thousand miles, but you had to dive a fair way beneath the sea to find your personal Manta Ray."}, + [358] = {name = "Blacknailed", grade = 1, points = 1, description = "Well, you can rest your nailcase now. This gravedigger's fingernails are nice and clean. Though after the next hellride, you might not want to let it hand any food to you."}, + [359] = {name = "Cartography 101", grade = 1, points = 2, description = "You succeeded in finding and charting several previously unexplored landmarks and locations for the Adventurer's Guild, you probably never need to ask anyone for the way - do you?"}, + [360] = {name = "Chequered Teddy", grade = 1, points = 1, description = "Don't let its fluffy appearance deceive you. The panda is a creature of the wild. It will take you to the most distant regions of the World, always in hopes of a little bamboo to nibble on or to check on a possible mate."}, + [361] = {name = "Dragon Mimicry", grade = 1, points = 2, description = "It's not really a dragon, but rather a kind of chimera. Nonetheless a decent mount to impress any passer-by."}, + [362] = {name = "Fabled Construction", grade = 1, points = 3, description = "Finding all the pieces to this complicated vehicle was one kind of a challenge. However, what you built in the end is rather a fabled than a feeble construction."}, + [363] = {name = "Fata Morgana", grade = 1, points = 2, description = "There are many delusions and phantasms in the desert. You saw a false oasis with fruit-bearing palm trees. Instead of water and refreshment, however, you found a dromedary in the end. What a useful Fata Morgana!"}, + [364] = {name = "Fried Shrimp", grade = 1, points = 2, description = "This must be underwater love - this enormous crustacean now does thy bidding. Or maybe it's just in it for a little more of that shrimp barbecue, as that's a little hard to come by in the sea."}, + [365] = {name = "Friend of Elves", grade = 1, points = 1, description = "Kingly deer mostly prefer elves as friends and familiars. This one, however, decided to favour you as a confidant and rider. Well done!"}, + [366] = {name = "Gear Up", grade = 1, points = 3, description = "Installing that control unit was a no-brainer. Now you're in control to make it walk this way or that, or to change tack at any moment if required. Your faithful walker mount obeys your every command."}, + [367] = {name = "Golden Sands", grade = 1, points = 3, description = "Counting ten thousand grains of sand could not have been harder than gaining this impressive mount."}, + [368] = {name = "Hoard of the Dragon", grade = 1, points = 1, secret = true, description = "Your adventurous way through countless dragon lairs earned you a pretty treasure - and surely the enmity of many a dragon."}, + [369] = {name = "Icy Glare", grade = 1, points = 1, description = "Here's looking at you, kid. This ancient creature seems to size you up with its brilliant eyes and barely tolerates you riding it. Maybe it thinks you're the defrosted snack, after all?"}, + [370] = {name = "Knock on Wood", grade = 1, points = 3, description = "It's a wound-up wooden lizard! Well, stranger things have happened, or so you're told. Just hop on and let this wood-and-tin contraption take you anywhere you want to wind down a bit. And hope you don't get hit by lightning underway."}, + [371] = {name = "Lion King", grade = 1, points = 1, description = "By mastering the secrets of Lion's Rock, you proved yourself worthy to face the mighty lions there. One of them even chose to accompany you."}, + [372] = {name = "Little Ball of Wool", grade = 1, points = 1, description = "You found a lost sheep and thus a steady source of black wool. But careful: don't get entangled."}, + [373] = {name = "Lost Palace Raider", grade = 1, points = 2, secret = true, description = "Lifting the secrets of a fabulous palace and defeating a beautiful demon princess was a thrilling experience indeed. This site's marvels nearly matched its terrors. Nearly."}, + [374] = {name = "Lovely Dots", grade = 1, points = 3, description = "Finding a four-leaved clover is always a sign of luck. And as luck would have it, you even baited a lovely dotted ladybug. Lucky you!"}, + [375] = {name = "Loyal Lad", grade = 1, points = 1, description = "Having a loyal friend alongside is comforting to every adventurer. If only this lad was not so stubborn..."}, + [376] = {name = "Lucky Horseshoe", grade = 1, points = 1, description = "'Sweets for my steed' could be your motto. An impressive horse is eating out of your hand. Saddle up and be ready to find adventure, new friends, and maybe someone to shoe your horse now and then."}, + [377] = {name = "Luminous Kitty", grade = 1, points = 3, description = "You made some efforts to bring a little more light into the world. And what a nice present you got in return!"}, + [378] = {name = "Magnetised", grade = 1, points = 2, description = "This magnetic beast attracted you in a very literal way. Or was it attracted by your metal equipment? Anyway, you seem to be stuck together now."}, + [379] = {name = "Mind the Dog!", grade = 1, points = 2, description = "Barking dogs never bite, as the saying goes. But this one clearly tried. In the end, however, you were able to walk the dog - ahem, gnarlhound."}, + [380] = {name = "Out of the Stone Age", grade = 1, points = 3, description = "What a blast from the past! This thankful patient thinks you missed your dentist vocation. It's now ready to take a bite of the future and to carry you to your next adventure, or your next patient."}, + [381] = {name = "Pecking Order", grade = 1, points = 1, description = "Ah, the old carrot-on-a-stick trick. Well done! You've made the racing bird accept you as a rider and provider. Just don't feed it your fingers."}, + [382] = {name = "Personal Nightmare", grade = 1, points = 3, description = "It might come as a shock to you, but this is the mount of your dreams. Not exactly the white steed of Prince Charming, but maybe the ladies will still scream and faint at the sight of you."}, + [383] = {name = "Pig-Headed", grade = 1, points = 2, description = "Whoa, sow long! This boar is like a force of nature, breaking through the undergrowth of all the forests and all records of speed. Hang on!"}, + [384] = {name = "Scales and Tail", grade = 1, points = 2, description = "The Muggy Plains are a dangerous place, often raided by dragons. But that was your luck: thus you found this scaly little guy."}, + [385] = {name = "Slugging Around", grade = 1, points = 2, description = "Drugging a snail can have some beneficial side effects. You're now the proud owner of a snarling, speed-crazy slug. Maybe it'll purr if you stroke it. Anyway, life should be one slick ride from now on."}, + [386] = {name = "Spin-Off", grade = 1, points = 1, description = "Seems like this spider has got a sweet tooth. As a result, eight hairy legs are now at your disposal to crawl and weave at your whim, and strike fear into the hearts of men."}, + [387] = {name = "Starless Night", grade = 1, points = 3, description = "By many it is considered a myth like the Yeti. But you came, saw and tamed it. Now you're the proud rider of a midnight panther, black as a starless night."}, + [388] = {name = "Stuntman", grade = 1, points = 3, description = "A drop of oil and you're good to go. This unique mount will roll merrily in and out of any strange place you want to visit. If you see no exit, you probably ended up in a circus ring. Ah well, the show must go on!"}, + [389] = {name = "Swamp Beast", grade = 1, points = 1, description = "By cleverly using a leech to cool that raging bull's blood, you managed not to get swamped or trampled in a water buffalo stampede. The creature is now docile and follows your every command."}, + [390] = {name = "The Right Tone", grade = 1, points = 1, description = "By setting the right tone you convinced a crystal wolf to accompany you. Remember it is made of crystal, though, so be careful in a banshee's presence."}, + [391] = {name = "Thick-Skinned", grade = 1, points = 2, description = "It's unstoppable! Walls? Fortresses? Obstacles? Objections? Pah! Nothing will stand before the stampor. Arrows and spears bounce off its hide, enemies are trampled by the dozen. Just don't go for the subtle approach or a date on this thing."}, + [392] = {name = "Way to Hell", grade = 1, points = 2, description = "This fiery beast really tried to give you hell. But not even a magma crawler can resist a mug of spicy, hot glow wine. Skol!"}, + + -- 10.9 + [393] = {name = "Hat Hunter", grade = 2, points = 5, description = "You sucessfully fought against all odds to protect your world from an ascending god! – You weren't there for the hat only after all?"}, + [394] = {name = "Ogre Chef", grade = 1, points = 1, description = "You didn't manage to become an ogre chief. But at least you are, beyond doubt, a worthy ogre chef."}, + [395] = {name = "Rift Warrior", grade = 1, points = 3, description = "You went through hell. Seven times. You defeated the demons. Countless times. You put an end to Ferumbras claims to ascendancy. Once and for all."}, + [396] = {name = "The Call of the Wild", grade = 1, points = 2, description = "You opposed man-eating ogres and clumsy clomps. You grappled with hungry chieftains, desperate goblins and angry spirits. So you truly overcame the wild vastness of Krailos."}, + + -- 10.94 + [397] = {name = "Ender of the End", grade = 2, points = 5, description = "You have entered the heart of destruction and valiantly defeated the world devourer. By your actions you have postponed the end of the world — at least for a while."}, + [398] = {name = "Vortex Tamer", grade = 2, points = 5, description = "After a long journey and dedication you were favoured by fortune and have tamed all three elusive beasts of the vortex. Unless the Vortexion decides you're a tasty morsel you can enjoy your small stable of ravaging beasts from beyond."}, +} + +ACHIEVEMENT_FIRST = 1 +ACHIEVEMENT_LAST = #achievements + +function getAchievementInfoById(id) + for k, v in pairs(achievements) do + if k == id then + local targetAchievement = {} + targetAchievement.id = k + targetAchievement.actionStorage = PlayerStorageKeys.achievementsCounter + k + for inf, it in pairs(v) do + targetAchievement[inf] = it + end + return targetAchievement + end + end + return false +end + +function getAchievementInfoByName(name) + for k, v in pairs(achievements) do + if v.name:lower() == name:lower() then + local targetAchievement = {} + targetAchievement.id = k + targetAchievement.actionStorage = PlayerStorageKeys.achievementsCounter + k + for inf, it in pairs(v) do + targetAchievement[inf] = it + end + return targetAchievement + end + end + return false +end + +function getSecretAchievements() + local targetAchievement = {} + for k, v in pairs(achievements) do + if v.secret then + targetAchievement[#targetAchievement + 1] = k + end + end + return targetAchievement +end + +function getPublicAchievements() + local targetAchievement = {} + for k, v in pairs(achievements) do + if not v.secret then + targetAchievement[#targetAchievement + 1] = k + end + end + return targetAchievement +end + +function getAchievements() + return achievements +end + +function isAchievementSecret(ach) + local achievement + if tonumber(ach) ~= nil then + achievement = getAchievementInfoById(ach) + else + achievement = getAchievementInfoByName(ach) + end + if not achievement then + print("[!] -> Invalid achievement \"" .. ach .. "\".") + return false + end + + return achievement.secret +end + +function Player.hasAchievement(self, ach) + local achievement + if tonumber(ach) ~= nil then + achievement = getAchievementInfoById(ach) + else + achievement = getAchievementInfoByName(ach) + end + if not achievement then + print("[!] -> Invalid achievement \"" .. ach .. "\".") + return false + end + + return self:getStorageValue(PlayerStorageKeys.achievementsBase + achievement.id) > 0 +end + +function Player.getAchievements(self) + local targetAchievement = {} + for k = 1, #achievements do + if self:hasAchievement(k) then + targetAchievement[#targetAchievement + 1] = k + end + end + return targetAchievement +end + +function Player.addAchievement(self, ach, hideMsg) + local achievement + if tonumber(ach) ~= nil then + achievement = getAchievementInfoById(ach) + else + achievement = getAchievementInfoByName(ach) + end + if not achievement then + print("[!] -> Invalid achievement \"" .. ach .. "\".") + return false + end + + if not self:hasAchievement(achievement.id) then + self:setStorageValue(PlayerStorageKeys.achievementsBase + achievement.id, 1) + if not hideMsg then + self:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Congratulations! You earned the achievement \"" .. achievement.name .. "\".") + end + end + return true +end + +function Player.removeAchievement(self, ach) + local achievement + if tonumber(ach) ~= nil then + achievement = getAchievementInfoById(ach) + else + achievement = getAchievementInfoByName(ach) + end + if not achievement then + print("[!] -> Invalid achievement \"" .. ach .. "\".") + return false + end + + if self:hasAchievement(achievement.id) then + self:setStorageValue(PlayerStorageKeys.achievementsBase + achievement.id, -1) + end + return true +end + +function Player.addAllAchievements(self, hideMsg) + for i = ACHIEVEMENT_FIRST, ACHIEVEMENT_LAST do + self:addAchievement(i, hideMsg) + end + return true +end + +function Player.removeAllAchievements(self) + for k = 1, #achievements do + if self:hasAchievement(k) then + self:removeAchievement(k) + end + end + return true +end + +function Player.getSecretAchievements(self) + local targetAchievement = {} + for k, v in pairs(achievements) do + if self:hasAchievement(k) and v.secret then + targetAchievement[#targetAchievement + 1] = k + end + end + return targetAchievement +end + +function Player.getPublicAchievements(self) + local targetAchievement = {} + for k, v in pairs(achievements) do + if self:hasAchievement(k) and not v.secret then + targetAchievement[#targetAchievement + 1] = k + end + end + return targetAchievement +end + +function Player.getAchievementPoints(self) + local points = 0 + local list = self:getAchievements() + if #list > 0 then -- has achievements + for i = 1, #list do + local targetAchievement = getAchievementInfoById(list[i]) + if targetAchievement.points > 0 then -- avoid achievements with unknow points + points = points + targetAchievement.points + end + end + end + return points +end + +function Player.addAchievementProgress(self, ach, value) + local achievement = tonumber(ach) ~= nil and getAchievementInfoById(ach) or getAchievementInfoByName(ach) + if not achievement then + print('[!] -> Invalid achievement "' .. ach .. '".') + return true + end + + local storage = PlayerStorageKeys.achievementsCounter + achievement.id + local progress = self:getStorageValue(storage) + if progress < value then + self:setStorageValue(storage, math.max(1, progress) + 1) + elseif progress == value then + self:setStorageValue(storage, value + 1) + self:addAchievement(achievement.id) + end + return true +end diff --git a/data/lib/core/actionids.lua b/data/lib/core/actionids.lua new file mode 100644 index 0000000..d4092e4 --- /dev/null +++ b/data/lib/core/actionids.lua @@ -0,0 +1,7 @@ +actionIds = { + sandHole = 100, -- hidden sand hole + pickHole = 105, -- hidden mud hole + levelDoor = 1000, -- level door + citizenship = 30020, -- citizenship teleport + citizenshipLast = 30050, -- citizenship teleport last +} diff --git a/data/lib/core/combat.lua b/data/lib/core/combat.lua new file mode 100644 index 0000000..236ab74 --- /dev/null +++ b/data/lib/core/combat.lua @@ -0,0 +1,21 @@ +function Combat:getPositions(creature, variant) + local positions = {} + function onTargetTile(creature, position) + positions[#positions + 1] = position + end + + self:setCallback(CALLBACK_PARAM_TARGETTILE, "onTargetTile") + self:execute(creature, variant) + return positions +end + +function Combat:getTargets(creature, variant) + local targets = {} + function onTargetCreature(creature, target) + targets[#targets + 1] = target + end + + self:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature") + self:execute(creature, variant) + return targets +end diff --git a/data/lib/core/container.lua b/data/lib/core/container.lua index 65343eb..7023ae9 100644 --- a/data/lib/core/container.lua +++ b/data/lib/core/container.lua @@ -1,3 +1,58 @@ function Container.isContainer(self) return true end + +function Container.createLootItem(self, item) + if self:getEmptySlots() == 0 then + return true + end + + local itemCount = 0 + local randvalue = getLootRandom() + if randvalue < item.chance then + if ItemType(item.itemId):isStackable() then + itemCount = randvalue % item.maxCount + 1 + else + itemCount = 1 + end + end + + if itemCount > 0 then + local tmpItem = Game.createItem(item.itemId, math.min(itemCount, 100)) + if not tmpItem then + return false + end + + if tmpItem:isContainer() then + for i = 1, #item.childLoot do + if not tmpItem:createLootItem(item.childLoot[i]) then + tmpItem:remove() + return false + end + end + + if #item.childLoot > 0 and tmpItem:getSize() == 0 then + tmpItem:remove() + return true + end + end + + if item.subType ~= -1 then + tmpItem:setAttribute(ITEM_ATTRIBUTE_CHARGES, item.subType) + end + + if item.actionId ~= -1 then + tmpItem:setActionId(item.actionId) + end + + if item.text and item.text ~= "" then + tmpItem:setText(item.text) + end + + local ret = self:addItemEx(tmpItem) + if ret ~= RETURNVALUE_NOERROR then + tmpItem:remove() + end + end + return true +end diff --git a/data/lib/core/core.lua b/data/lib/core/core.lua index 1085993..ef4d6e2 100644 --- a/data/lib/core/core.lua +++ b/data/lib/core/core.lua @@ -1,9 +1,16 @@ +-- Note: The library of storages must be loaded previously to the other libraries. +dofile('data/lib/core/storages.lua') + +dofile('data/lib/core/achievements.lua') +dofile('data/lib/core/actionids.lua') +dofile('data/lib/core/combat.lua') dofile('data/lib/core/constants.lua') dofile('data/lib/core/container.lua') dofile('data/lib/core/creature.lua') dofile('data/lib/core/game.lua') dofile('data/lib/core/item.lua') dofile('data/lib/core/itemtype.lua') +dofile('data/lib/core/party.lua') dofile('data/lib/core/player.lua') dofile('data/lib/core/position.lua') dofile('data/lib/core/teleport.lua') diff --git a/data/lib/core/creature.lua b/data/lib/core/creature.lua index 95fda6a..c01b4cd 100644 --- a/data/lib/core/creature.lua +++ b/data/lib/core/creature.lua @@ -19,7 +19,7 @@ function Creature.getClosestFreePosition(self, position, maxRadius, mustBeReacha end local tile = Tile(checkPosition) - if tile:getCreatureCount() == 0 and not tile:hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID) and + if tile and tile:getCreatureCount() == 0 and not tile:hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID) and (not mustBeReachable or self:getPathTo(checkPosition)) then return checkPosition end @@ -105,6 +105,7 @@ function Creature:addSummon(monster) summon:setDropLoot(false) summon:setSkillLoss(false) summon:setMaster(self) + summon:getPosition():notifySummonAppear(summon) return true end @@ -166,3 +167,10 @@ function Creature:addDamageCondition(target, type, list, damage, period, rounds) target:addCondition(condition) return true end + +function Creature:canAccessPz() + if self:isMonster() or (self:isPlayer() and self:isPzLocked()) then + return false + end + return true +end diff --git a/data/lib/core/item.lua b/data/lib/core/item.lua index f036872..983b1d4 100644 --- a/data/lib/core/item.lua +++ b/data/lib/core/item.lua @@ -29,3 +29,531 @@ end function Item.isTile(self) return false end + +-- Helper class to make string formatting prettier + +StringStream = {} + +setmetatable(StringStream, { + __call = function(self) + local obj = {} + return setmetatable(obj, {__index = StringStream}) + end +}) + +function StringStream.append(self, str, ...) + self[#self+1] = string.format(str, ...) +end + +function StringStream.concat(self, sep) + return table.concat(self, sep) +end + +local aux = { + ['Defense'] = {key = ITEM_ATTRIBUTE_DEFENSE}, + ['ExtraDefense'] = {key = ITEM_ATTRIBUTE_EXTRADEFENSE}, + ['Attack'] = {key = ITEM_ATTRIBUTE_ATTACK}, + ['AttackSpeed'] = {key = ITEM_ATTRIBUTE_ATTACK_SPEED}, + ['HitChance'] = {key = ITEM_ATTRIBUTE_HITCHANCE}, + ['ShootRange'] = {key = ITEM_ATTRIBUTE_SHOOTRANGE}, + ['Armor'] = {key = ITEM_ATTRIBUTE_ARMOR}, + ['Duration'] = {key = ITEM_ATTRIBUTE_DURATION, cmp = function(v) return v > 0 end}, + ['Text'] = {key = ITEM_ATTRIBUTE_TEXT, cmp = function(v) return v ~= '' end}, + ['Date'] = {key = ITEM_ATTRIBUTE_DATE}, + ['Writer'] = {key = ITEM_ATTRIBUTE_WRITER, cmp = function(v) return v ~= '' end} +} + +function setAuxFunctions() + for name, def in pairs(aux) do + Item['get'.. name] = function(self) + local attr = self:getAttribute(def.key) + if def.cmp and def.cmp(attr) then + return attr + elseif not def.cmp and attr and attr ~= 0 then + return attr + end + local default = ItemType['get'.. name] + return default and default(self:getType()) or nil + end + end +end + +setAuxFunctions() + +do + local function internalItemGetNameDescription(it, item, subType, addArticle) + local subType = subType or (item and item:getSubType() or -1) + local ss = StringStream() + local obj = item or it + local name = obj:getName() + if name ~= '' then + if it:isStackable() and subType > 1 then + if it:hasShowCount() then + ss:append('%d ', subType) + end + ss:append('%s', obj:getPluralName()) + else + if addArticle and obj:getArticle() ~= '' then + ss:append('%s ', obj:getArticle()) + end + ss:append('%s', obj:getName()) + end + else + ss:append('an item of type %d', obj:getId()) + end + return ss:concat() + end + + function Item.getNameDescription(self, subType, addArticle) + return internalItemGetNameDescription(self:getType(), self, subType, addArticle) + end + + function ItemType.getNameDescription(self, subType, addArticle) + return internalItemGetNameDescription(self, nil, subType, addArticle) + end +end + +do + local function addSeparator(ss, begin) + if begin then + begin = false + ss:append(' (') + else + ss:append(', ') + end + return begin + end + + local function addGenerics(item, it, abilities, ss, begin) + local obj = item or it + if it:getWeaponType() == WEAPON_DISTANCE and it:getAmmoType() ~= 0 then + ss:append(' (Range:%d', obj:getShootRange()) + local attack = obj:getAttack() + local hitChance = obj:getHitChance() + if attack ~= 0 then + ss:append(', Atk%s%d', showpos(attack), math.abs(attack)) + end + + if hitChance ~= 0 then + ss:append(', Hit%%%s%d', showpos(hitChance), math.abs(hitChance)) + end + + begin = false + elseif it:getWeaponType() ~= WEAPON_AMMO then + local attack = obj:getAttack() + local defense = obj:getDefense() + local extraDefense = obj:getExtraDefense() + + if attack ~= 0 then + begin = false + ss:append(' (Atk:%d', attack) + + if abilities.elementType ~= COMBAT_NONE and abilities.elementDamage ~= 0 then + ss:append(' physical + %d %s', abilities.elementDamage, getCombatName(abilities.elementType)) + end + end + + if defense ~= 0 or extraDefense ~= 0 then + begin = addSeparator(ss, begin) + ss:append('Def:%d', defense) + if extraDefense ~= 0 then + ss:append(' %s%d', showpos(extraDefense), math.abs(extraDefense)) + end + end + end + + -- Skills + for skill, value in ipairs(abilities.skills) do + if value ~= 0 then + begin = addSeparator(ss, begin) + ss:append('%s %s%d', getSkillName(skill - 1), showpos(value), math.abs(value)) + end + end + + -- Special Skills + for specialSkill, value in ipairs(abilities.specialSkills) do + if value ~= 0 then + begin = addSeparator(ss, begin) + ss:append('%s %s%d%%', getSpecialSkillName(specialSkill - 1), showpos(value), math.abs(value)) + end + end + + local magicPoints = abilities.stats[4] + if magicPoints ~= 0 then + begin = addSeparator(ss, begin) + ss:append('magic level %s%d', showpos(magicPoints), math.abs(magicPoints)) + end + + -- Absorb + + local show = abilities.absorbPercent[1] + if show ~= 0 then + for _, value in ipairs(abilities.absorbPercent) do + if value ~= show then + show = 0 + end + end + end + + if show == 0 then + local tmp = true + for i, value in ipairs(abilities.absorbPercent) do + if value ~= 0 then + if tmp then + tmp = false + begin = addSeparator(ss, begin) + ss:append('protection ') + else + ss:append(', ') + end + ss:append('%s %s%d%%', getCombatName(indexToCombatType(i - 1)), showpos(value), math.abs(value)) + end + end + else + begin = addSeparator(ss, begin) + ss:append('protection all %s%d%%', showpos(show), math.abs(show)) + end + + -- Field absorb + + local show = abilities.fieldAbsorbPercent[1] + if show ~= 0 then + for _, value in ipairs(abilities.fieldAbsorbPercent) do + if value ~= show then + show = 0 + end + end + end + + if show == 0 then + local tmp = true + for i, value in ipairs(abilities.fieldAbsorbPercent) do + if value ~= 0 then + if tmp then + tmp = false + begin = addSeparator(ss, begin) + ss:append('protection ') + else + ss:append(', ') + end + ss:append('%s field %s%d%%', getCombatName(indexToCombatType(i - 1)), showpos(value), math.abs(value)) + end + end + else + begin = addSeparator(ss, begin) + ss:append('protection all fields %s%d%%', showpos(show), math.abs(show)) + end + + if abilities.speed ~= 0 then + begin = addSeparator(ss, begin) + ss:append('speed %s%d', showpos(abilities.speed), math.abs(abilities.speed / 2)) + end + return begin + end + + local function internalItemGetDescription(it, lookDistance, item, subType, addArticle) + local abilities = it:getAbilities() + local ss = StringStream() + local subType = subType or (item and item:getSubType() or -1) + local text = nil + local begin = true + local obj = item or it + + if item then + ss:append(item:getNameDescription(subType, addArticle or true)) + else + ss:append(it:getNameDescription(subType, addArticle or true)) + end + + if it:isRune() then + local rune = Spell(it:getId()) + if rune then + if rune:runeLevel() and rune:runeLevel() > 0 or rune:runeMagicLevel() and rune:runeMagicLevel() > 0 then + local tmpVocMap = rune:vocation() + local vocMap = {} + for k, vocName in ipairs(tmpVocMap) do + local vocation = Vocation(vocName) + if vocation and vocation:getPromotion() then + vocMap[#vocMap + 1] = vocName + end + end + + ss:append('. %s can only be used by', it:isStackable() and subType > 1 and 'They' or 'It') + + -- Only show base vocations in description; promotions should be a given + if #vocMap == 0 then + ss:append(' players') + else + for i = 1, #vocMap - 1 do + local vocName = vocMap[i] + local vocation = Vocation(vocName) + ss:append(' %ss', vocName:lower()) + if i + 1 == #vocMap then + ss:append(' and') + else + ss:append(',') + end + end + local vocName = vocMap[#vocMap] + ss:append(' %ss', vocName:lower()) + end + + ss:append(' with') + + if rune:runeLevel() > 0 then + ss:append(' level %d', rune:runeLevel()) + end + + if rune:runeMagicLevel() > 0 then + if rune:runeLevel() > 0 then + ss:append(' and ') + end + ss:append('magic level %d', rune:runeMagicLevel()) + end + + ss:append(' or higher') + end + + if not begin then + ss:append(')') + end + end + elseif it:getWeaponType() ~= WEAPON_NONE then + begin = addGenerics(item, it, abilities, ss, begin) + if not begin then + ss:append(')') + end + elseif obj:getArmor() ~= 0 or it:hasShowAttributes() then + if obj:getArmor() ~= 0 then + ss:append(' (Arm:%d', obj:getArmor()) + begin = false + end + begin = addGenerics(item, it, abilities, ss, begin) + if not begin then + ss:append(')') + end + elseif it:isContainer() or item and item:isContainer() then + local volume = 0 + + if not item or not item:hasAttribute(ITEM_ATTRIBUTE_UNIQUEID) then + if it:isContainer() then + volume = it:getCapacity() + else + volume = item:getCapacity() + end + end + + if volume ~= 0 then + ss:append(' (Vol:%d)', volume) + end + else + local found = true + + if abilities.speed > 0 then + ss:append(' (speed %s%d)', showpos(abilities.speed), math.abs(abilities.speed / 2)) + elseif bit.band(abilities.conditionSuppressions, CONDITION_DRUNK) == 1 then + ss:append(' (hard drinking)') + elseif abilities.invisible then + ss:append(' (invisibility)') + elseif abilities.regeneration then + ss:append(' (faster regeneration)') + elseif abilities.manashield then + ss:append(' (mana shield)') + else + found = false + end + + if not found then + if it:getType() == ITEM_TYPE_KEY then + local aid = item and item:getActionId() or 0 + ss:append(' (Key:%s)', ('0'):rep(4 - #tostring(aid)) .. aid) + elseif it:getGroup() == ITEM_GROUP_FLUID then + if subType > 0 then + local name = getSubTypeName(subType) + ss:append(' of %s', name ~= '' and name or 'unknown') + else + ss:append('. It is empty') + end + elseif it:getGroup() == ITEM_GROUP_SPLASH then + local name = getSubTypeName(subType) + ss:append(' of ') + if subType > 0 and name ~= '' then + ss:append(name) + else + ss:append('unknown') + end + elseif it:hasAllowDistRead() and (it:getId() < 7369 or it:getId() > 7371) then + ss:append('.\n') + if lookDistance <= 4 then + if item then + if not text then + text = item:getText() + end + if text then + local writer = item:getWriter() + if writer then + local date = item:getDate() + ss:append('%s wrote', writer) + if date then + ss:append(' on %s', os.date('%d %b %Y')) + end + ss:append(': ') + else + ss:append('You read: ') + end + ss:append(text) + else + ss:append('Nothing is written on it') + end + else + ss:append('Nothing is written on it') + end + else + ss:append('You are too far away to read it.') + end + elseif it:getLevelDoor() ~= 0 and item then + local aid = item:getActionId() + if aid >= it:getLevelDoor() then + ss:append(' for level %d', aid - it:getLevelDoor()) + end + end + end + end + + if it:hasShowCharges() then + ss:append(' that has %d charge%s left', subType, subType ~= -1 and 's' or '') + end + + -- show duration + if it:hasShowDuration() then + if item and item:hasAttribute(ITEM_ATTRIBUTE_DURATION) then + local duration = item:getDuration() / 1000 + if duration > 0 then + ss:append(' that will expire in ') + + if duration >= 86400 then + local days = math.floor(duration / 86400) + local hours = math.floor((duration % 86400) / 3600) + ss:append('%d day%s', days, days ~= 1 and 's' or '') + if hours > 0 then + ss:append(' and %d hour%s', hours, hours ~= 1 and 's' or '') + end + elseif duration >= 3600 then + local hours = math.floor(duration / 3600) + local minutes = math.floor((duration % 3600) / 60) + ss:append('%d hour%s', hours, hours ~= 1 and 's' or '') + if minutes > 0 then + ss:append(' and %d minute%s', minutes, minutes ~= 1 and 's' or '') + end + elseif duration >= 60 then + local minutes = math.floor(duration / 60) + local seconds = duration % 60 + ss:append('%d minute%s', minutes, minutes ~= 1 and 's' or '') + if seconds > 0 then + ss:append(' and %d second%s', seconds, seconds ~= 1 and 's' or '') + end + else + ss:append('%d second%s', duration, duration ~= 1 and 's' or '') + end + end + else + ss:append(' that is brand-new') + end + end + + if not it:hasAllowDistRead() or (it:getId() >= 7369 and it:getId() <= 7371) then + ss:append('.') + else + if not text and item then + text = item:getText() + end + if not text or text == '' then + ss:append('.') + end + end + + local wieldInfo = it:getWieldInfo() + if wieldInfo ~= 0 then + ss:append('\nIt can only be wielded properly by ') + + if bit.band(wieldInfo, WIELDINFO_PREMIUM) ~= 0 then + ss:append('premium ') + end + + local vocStr = it:getVocationString() + if vocStr ~= '' then + ss:append(vocStr) + else + ss:append('players') + end + + if bit.band(wieldInfo, WIELDINFO_LEVEL) ~= 0 then + ss:append(' of level %d or higher', it:getMinReqLevel()) + end + + if bit.band(wieldInfo, WIELDINFO_MAGLV) ~= 0 then + if bit.band(wieldInfo, WIELDINFO_LEVEL) ~= 0 then + ss:append(' and') + else + ss:append(' of') + end + ss:append(' magic level %d or higher', it:getMinReqMagicLevel()) + end + ss:append('.') + end + + if lookDistance <= 1 then + local weight = obj:getWeight() + local count = item and item:getCount() or 1 + if weight ~= 0 and it:isPickupable() then + ss:append('\n') + if it:isStackable() and count > 1 and it:hasShowCount() then + ss:append('They weigh ') + else + ss:append('It weighs ') + end + ss:append('%.2f oz.', weight / 100) + end + end + + local desc = it:getDescription() + if item then + local specialDesc = item:getSpecialDescription() + if specialDesc ~= '' then + ss:append('\n%s', specialDesc) + elseif lookDistance <= 1 and desc ~= '' then + ss:append('\n%s', desc) + end + elseif lookDistance <= 1 and desc ~= '' then + ss:append('\n%s', it:getDescription()) + end + + if it:hasAllowDistRead() or (it:getId() >= 7369 and it:getId() <= 7371) then + if not text and item then + text = item:getText() + end + + if text and text ~= '' then + ss:append('\n%s', text) + end + end + + return ss:concat() + end + + if not oldItemDesc then + oldItemDesc = Item.getDescription + end + + if configManager.getBoolean(configKeys.LUA_ITEM_DESC) then + function Item.getDescription(self, lookDistance, subType) + return internalItemGetDescription(self:getType(), lookDistance, self, subType) + end + + function ItemType.getItemDescription(self, lookDistance, subType) + return internalItemGetDescription(self, lookDistance, nil, subType) + end + else + Item.getDescription = oldItemDesc + end +end diff --git a/data/lib/core/party.lua b/data/lib/core/party.lua new file mode 100644 index 0000000..64fef7f --- /dev/null +++ b/data/lib/core/party.lua @@ -0,0 +1,10 @@ +function Party.broadcastPartyLoot(self, text) + self:getLeader():sendTextMessage(MESSAGE_INFO_DESCR, text) + local membersList = self:getMembers() + for i = 1, #membersList do + local player = membersList[i] + if player then + player:sendTextMessage(MESSAGE_INFO_DESCR, text) + end + end +end diff --git a/data/lib/core/player.lua b/data/lib/core/player.lua index ab1ad95..72be9c1 100644 --- a/data/lib/core/player.lua +++ b/data/lib/core/player.lua @@ -36,17 +36,17 @@ function Player.hasFlag(self, flag) return self:getGroup():hasFlag(flag) end -local lossPercent = { - [0] = 100, - [1] = 70, - [2] = 45, - [3] = 25, - [4] = 10, - [5] = 0 -} - function Player.getLossPercent(self) local blessings = 0 + local lossPercent = { + [0] = 100, + [1] = 70, + [2] = 45, + [3] = 25, + [4] = 10, + [5] = 0 + } + for i = 1, 5 do if self:hasBlessing(i) then blessings = blessings + 1 @@ -55,8 +55,44 @@ function Player.getLossPercent(self) return lossPercent[blessings] end +function Player.getPremiumTime(self) + return math.max(0, self:getPremiumEndsAt() - os.time()) +end + +function Player.setPremiumTime(self, seconds) + self:setPremiumEndsAt(os.time() + seconds) + return true +end + +function Player.addPremiumTime(self, seconds) + self:setPremiumTime(self:getPremiumTime() + seconds) + return true +end + +function Player.removePremiumTime(self, seconds) + local currentTime = self:getPremiumTime() + if currentTime < seconds then + return false + end + + self:setPremiumTime(currentTime - seconds) + return true +end + +function Player.getPremiumDays(self) + return math.floor(self:getPremiumTime() / 86400) +end + +function Player.addPremiumDays(self, days) + return self:addPremiumTime(days * 86400) +end + +function Player.removePremiumDays(self, days) + return self:removePremiumTime(days * 86400) +end + function Player.isPremium(self) - return self:getPremiumDays() > 0 or configManager.getBoolean(configKeys.FREE_PREMIUM) + return self:getPremiumTime() > 0 or configManager.getBoolean(configKeys.FREE_PREMIUM) or self:hasFlag(PlayerFlag_IsAlwaysPremium) end function Player.sendCancelMessage(self, message) @@ -100,3 +136,199 @@ function Player.addManaSpent(...) APPLY_SKILL_MULTIPLIER = true return ret end + +-- Always pass the number through the isValidMoney function first before using the transferMoneyTo +function Player.transferMoneyTo(self, target, amount) + if not target then + return false + end + + -- See if you can afford this transfer + local balance = self:getBankBalance() + if amount > balance then + return false + end + + -- See if player is online + local targetPlayer = Player(target.guid) + if targetPlayer then + targetPlayer:setBankBalance(targetPlayer:getBankBalance() + amount) + else + db.query("UPDATE `players` SET `balance` = `balance` + " .. amount .. " WHERE `id` = '" .. target.guid .. "'") + end + + self:setBankBalance(self:getBankBalance() - amount) + return true +end + +function Player.canCarryMoney(self, amount) + -- Anyone can carry as much imaginary money as they desire + if amount == 0 then + return true + end + + -- The 3 below loops will populate these local variables + local totalWeight = 0 + local inventorySlots = 0 + + -- Add crystal coins to totalWeight and inventorySlots + local type_crystal = ItemType(ITEM_CRYSTAL_COIN) + local crystalCoins = math.floor(amount / 10000) + if crystalCoins > 0 then + amount = amount - (crystalCoins * 10000) + while crystalCoins > 0 do + local count = math.min(100, crystalCoins) + totalWeight = totalWeight + type_crystal:getWeight(count) + crystalCoins = crystalCoins - count + inventorySlots = inventorySlots + 1 + end + end + + -- Add platinum coins to totalWeight and inventorySlots + local type_platinum = ItemType(ITEM_PLATINUM_COIN) + local platinumCoins = math.floor(amount / 100) + if platinumCoins > 0 then + amount = amount - (platinumCoins * 100) + while platinumCoins > 0 do + local count = math.min(100, platinumCoins) + totalWeight = totalWeight + type_platinum:getWeight(count) + platinumCoins = platinumCoins - count + inventorySlots = inventorySlots + 1 + end + end + + -- Add gold coins to totalWeight and inventorySlots + local type_gold = ItemType(ITEM_GOLD_COIN) + if amount > 0 then + while amount > 0 do + local count = math.min(100, amount) + totalWeight = totalWeight + type_gold:getWeight(count) + amount = amount - count + inventorySlots = inventorySlots + 1 + end + end + + -- If player don't have enough capacity to carry this money + if self:getFreeCapacity() < totalWeight then + return false + end + + -- If player don't have enough available inventory slots to carry this money + local backpack = self:getSlotItem(CONST_SLOT_BACKPACK) + if not backpack or backpack:getEmptySlots(true) < inventorySlots then + return false + end + return true +end + +function Player.withdrawMoney(self, amount) + local balance = self:getBankBalance() + if amount > balance or not self:addMoney(amount) then + return false + end + + self:setBankBalance(balance - amount) + return true +end + +function Player.depositMoney(self, amount) + if not self:removeMoney(amount) then + return false + end + + self:setBankBalance(self:getBankBalance() + amount) + return true +end + +function Player.removeTotalMoney(self, amount) + local moneyCount = self:getMoney() + local bankCount = self:getBankBalance() + if amount <= moneyCount then + self:removeMoney(amount) + return true + elseif amount <= (moneyCount + bankCount) then + if moneyCount ~= 0 then + self:removeMoney(moneyCount) + local remains = amount - moneyCount + self:setBankBalance(bankCount - remains) + self:sendTextMessage(MESSAGE_INFO_DESCR, ("Paid %d from inventory and %d gold from bank account. Your account balance is now %d gold."):format(moneyCount, amount - moneyCount, self:getBankBalance())) + return true + else + self:setBankBalance(bankCount - amount) + self:sendTextMessage(MESSAGE_INFO_DESCR, ("Paid %d gold from bank account. Your account balance is now %d gold."):format(amount, self:getBankBalance())) + return true + end + end + return false +end + +function Player.addLevel(self, amount, round) + round = round or false + local level, amount = self:getLevel(), amount or 1 + if amount > 0 then + return self:addExperience(Game.getExperienceForLevel(level + amount) - (round and self:getExperience() or Game.getExperienceForLevel(level))) + else + return self:removeExperience(((round and self:getExperience() or Game.getExperienceForLevel(level)) - Game.getExperienceForLevel(level + amount))) + end +end + +function Player.addMagicLevel(self, value) + local currentMagLevel = self:getBaseMagicLevel() + local sum = 0 + + if value > 0 then + while value > 0 do + sum = sum + self:getVocation():getRequiredManaSpent(currentMagLevel + value) + value = value - 1 + end + + return self:addManaSpent(sum - self:getManaSpent()) + else + value = math.min(currentMagLevel, math.abs(value)) + while value > 0 do + sum = sum + self:getVocation():getRequiredManaSpent(currentMagLevel - value + 1) + value = value - 1 + end + + return self:removeManaSpent(sum + self:getManaSpent()) + end +end + +function Player.addSkillLevel(self, skillId, value) + local currentSkillLevel = self:getSkillLevel(skillId) + local sum = 0 + + if value > 0 then + while value > 0 do + sum = sum + self:getVocation():getRequiredSkillTries(skillId, currentSkillLevel + value) + value = value - 1 + end + + return self:addSkillTries(skillId, sum - self:getSkillTries(skillId)) + else + value = math.min(currentSkillLevel, math.abs(value)) + while value > 0 do + sum = sum + self:getVocation():getRequiredSkillTries(skillId, currentSkillLevel - value + 1) + value = value - 1 + end + + return self:removeSkillTries(skillId, sum + self:getSkillTries(skillId), true) + end +end + +function Player.addSkill(self, skillId, value, round) + if skillId == SKILL_LEVEL then + return self:addLevel(value, round or false) + elseif skillId == SKILL_MAGLEVEL then + return self:addMagicLevel(value) + end + return self:addSkillLevel(skillId, value) +end + +function Player.getWeaponType(self) + local weapon = self:getSlotItem(CONST_SLOT_LEFT) + if weapon then + return weapon:getType():getWeaponType() + end + return WEAPON_NONE +end diff --git a/data/lib/core/position.lua b/data/lib/core/position.lua index 8b97d7e..a40f8ea 100644 --- a/data/lib/core/position.lua +++ b/data/lib/core/position.lua @@ -48,7 +48,7 @@ function Position:moveUpstairs() end function Position:isInRange(from, to) - -- No matter what corner from and to is, we want to make + -- No matter what corner from and to is, we want to make -- life easier by calculating north-west and south-east local zone = { nW = { @@ -64,9 +64,18 @@ function Position:isInRange(from, to) } if self.x >= zone.nW.x and self.x <= zone.sE.x - and self.y >= zone.nW.y and self.y <= zone.sE.y + and self.y >= zone.nW.y and self.y <= zone.sE.y and self.z >= zone.nW.z and self.z <= zone.sE.z then return true end return false end + +function Position:notifySummonAppear(summon) + local spectators = Game.getSpectators(self) + for _, spectator in ipairs(spectators) do + if spectator:isMonster() and spectator ~= summon then + spectator:addTarget(summon) + end + end +end diff --git a/data/lib/core/storages.lua b/data/lib/core/storages.lua new file mode 100644 index 0000000..26f3410 --- /dev/null +++ b/data/lib/core/storages.lua @@ -0,0 +1,20 @@ +--[[ +Reserved storage ranges: +- 300000 to 301000+ reserved for achievements +- 20000 to 21000+ reserved for achievement progress +- 10000000 to 20000000 reserved for outfits and mounts on source +]]-- +PlayerStorageKeys = { + annihilatorReward = 30015, + promotion = 30018, + delayLargeSeaShell = 30019, + firstRod = 30020, + delayWallMirror = 30021, + madSheepSummon = 30023, + crateUsable = 30024, + achievementsBase = 300000, + achievementsCounter = 20000, +} + +GlobalStorageKeys = { +} diff --git a/data/lib/custom/custom.lua b/data/lib/custom/---custom.lua similarity index 100% rename from data/lib/custom/custom.lua rename to data/lib/custom/---custom.lua diff --git a/data/lib/custom/compat.lua b/data/lib/custom/compat.lua new file mode 100644 index 0000000..8ac17bd --- /dev/null +++ b/data/lib/custom/compat.lua @@ -0,0 +1,258 @@ +do + local function CreatureIndex(self, key) + local methods = getmetatable(self) + if key == "uid" then + return methods.getId(self) + elseif key == "type" then + local creatureType = 0 + if methods.isPlayer(self) then + creatureType = THING_TYPE_PLAYER + elseif methods.isMonster(self) then + creatureType = THING_TYPE_MONSTER + elseif methods.isNpc(self) then + creatureType = THING_TYPE_NPC + end + return creatureType + elseif key == "itemid" then + return 1 + elseif key == "actionid" then + return 0 + end + return methods[key] + end + rawgetmetatable("Player").__index = CreatureIndex + rawgetmetatable("Monster").__index = CreatureIndex + rawgetmetatable("Npc").__index = CreatureIndex +end + +do + local function ItemIndex(self, key) + local methods = getmetatable(self) + if key == "itemid" then + return methods.getId(self) + elseif key == "actionid" then + return methods.getActionId(self) + elseif key == "uid" then + return methods.getUniqueId(self) + elseif key == "type" then + return methods.getSubType(self) + end + return methods[key] + end + rawgetmetatable("Item").__index = ItemIndex + rawgetmetatable("Container").__index = ItemIndex + rawgetmetatable("Teleport").__index = ItemIndex +end + +do + local function ActionNewIndex(self, key, value) + if key == "onUse" then + self:onUse(value) + return + end + rawset(self, key, value) + end + rawgetmetatable("Action").__newindex = ActionNewIndex +end + +do + local function TalkActionNewIndex(self, key, value) + if key == "onSay" then + self:onSay(value) + return + end + rawset(self, key, value) + end + rawgetmetatable("TalkAction").__newindex = TalkActionNewIndex +end + +do + local function CreatureEventNewIndex(self, key, value) + if key == "onLogin" then + self:type("login") + self:onLogin(value) + return + elseif key == "onLogout" then + self:type("logout") + self:onLogout(value) + return + elseif key == "onThink" then + self:type("think") + self:onThink(value) + return + elseif key == "onPrepareDeath" then + self:type("preparedeath") + self:onPrepareDeath(value) + return + elseif key == "onDeath" then + self:type("death") + self:onDeath(value) + return + elseif key == "onKill" then + self:type("kill") + self:onKill(value) + return + elseif key == "onAdvance" then + self:type("advance") + self:onAdvance(value) + return + elseif key == "onModalWindow" then + self:type("modalwindow") + self:onModalWindow(value) + return + elseif key == "onTextEdit" then + self:type("textedit") + self:onTextEdit(value) + return + elseif key == "onHealthChange" then + self:type("healthchange") + self:onHealthChange(value) + return + elseif key == "onManaChange" then + self:type("manachange") + self:onManaChange(value) + return + elseif key == "onExtendedOpcode" then + self:type("extendedopcode") + self:onExtendedOpcode(value) + return + end + rawset(self, key, value) + end + rawgetmetatable("CreatureEvent").__newindex = CreatureEventNewIndex +end + +do + local function MoveEventNewIndex(self, key, value) + if key == "onEquip" then + self:type("equip") + self:onEquip(value) + return + elseif key == "onDeEquip" then + self:type("deequip") + self:onDeEquip(value) + return + elseif key == "onAddItem" then + self:type("additem") + self:onAddItem(value) + return + elseif key == "onRemoveItem" then + self:type("removeitem") + self:onRemoveItem(value) + return + elseif key == "onStepIn" then + self:type("stepin") + self:onStepIn(value) + return + elseif key == "onStepOut" then + self:type("stepout") + self:onStepOut(value) + return + end + rawset(self, key, value) + end + rawgetmetatable("MoveEvent").__newindex = MoveEventNewIndex +end + +do + local function GlobalEventNewIndex(self, key, value) + if key == "onThink" then + self:onThink(value) + return + elseif key == "onTime" then + self:onTime(value) + return + elseif key == "onStartup" then + self:type("startup") + self:onStartup(value) + return + elseif key == "onShutdown" then + self:type("shutdown") + self:onShutdown(value) + return + elseif key == "onRecord" then + self:type("record") + self:onRecord(value) + return + end + rawset(self, key, value) + end + rawgetmetatable("GlobalEvent").__newindex = GlobalEventNewIndex +end + +do + local function WeaponNewIndex(self, key, value) + if key == "onUseWeapon" then + self:onUseWeapon(value) + return + end + rawset(self, key, value) + end + rawgetmetatable("Weapon").__newindex = WeaponNewIndex +end + +do + local function SpellNewIndex(self, key, value) + if key == "onCastSpell" then + self:onCastSpell(value) + return + end + rawset(self, key, value) + end + rawgetmetatable("Spell").__newindex = SpellNewIndex +end + +do + local function MonsterTypeNewIndex(self, key, value) + if key == "onThink" then + self:eventType(MONSTERS_EVENT_THINK) + self:onThink(value) + return + elseif key == "onAppear" then + self:eventType(MONSTERS_EVENT_APPEAR) + self:onAppear(value) + return + elseif key == "onDisappear" then + self:eventType(MONSTERS_EVENT_DISAPPEAR) + self:onDisappear(value) + return + elseif key == "onMove" then + self:eventType(MONSTERS_EVENT_MOVE) + self:onMove(value) + return + elseif key == "onSay" then + self:eventType(MONSTERS_EVENT_SAY) + self:onSay(value) + return + end + rawset(self, key, value) + end + rawgetmetatable("MonsterType").__newindex = MonsterTypeNewIndex +end + +function createFunctions(class) + local exclude = {[2] = {"is"}, [3] = {"get", "set", "add", "can"}, [4] = {"need"}} + local temp = {} + for name, func in pairs(class) do + local add = true + for strLen, strTable in pairs(exclude) do + if table.contains(strTable, name:sub(1, strLen)) then + add = false + end + end + if add then + local str = name:sub(1, 1):upper() .. name:sub(2) + local getFunc = function(self) return func(self) end + local setFunc = function(self, ...) return func(self, ...) end + local get = "get" .. str + local set = "set" .. str + if not (rawget(class, get) and rawget(class, set)) then + table.insert(temp, {set, setFunc, get, getFunc}) + end + end + end + for _, func in ipairs(temp) do + rawset(class, func[1], func[2]) + rawset(class, func[3], func[4]) + end +end diff --git a/data/lib/custom/rewardboss.lua b/data/lib/custom/rewardboss.lua deleted file mode 100644 index 262bc67..0000000 --- a/data/lib/custom/rewardboss.lua +++ /dev/null @@ -1,130 +0,0 @@ -if not globalBosses then - globalBosses = {} -end - -function Monster.setReward(self, enable) - if enable then - if not self:getType():isRewardBoss() then - error("Rewards can only be enabled to rewards bosses.") - return false - end - globalBosses[self:getId()] = {} - self:registerEvent("BossDeath") - self:registerEvent("BossThink") - else - globalBosses[self:getId()] = nil - self:unregisterEvent("BossDeath") - self:unregisterEvent("BossThink") - end - return true -end - -local function pushValues(buffer, sep, ...) - local argv = {...} - local argc = #argv - for k, v in ipairs(argv) do - table.insert(buffer, v) - if k < argc and sep then - table.insert(buffer, sep) - end - end -end - -function Item.getNameDescription(self) - local subType = self:getSubType() - local itemType = self:getType() - - local buffer = {} - - local name = self:getName() or '' - if(#name ~= 0) then - if(itemType:isStackable() and subType > 1) then - pushValues(buffer, ' ', subType, self:getPluralName()) - else - local article = self:getArticle() or '' - pushValues(buffer, ' ', select(#article ~= 0 and 1 or 2, article, name)) - end - else - pushValues(buffer, ' ', 'an item of type', self:getId()) - end - - return table.concat(buffer) -end - -function Container.getContentDescription(self, outputBuffer) - local firstItem = true - local buffer = outputBuffer or {} - for i = 1, self:getSize() do - local item = self:getItem(i - 1) - - if(firstItem) then - firstItem = false - else - table.insert(buffer, ", ") - end - - table.insert(buffer, item:getNameDescription()) - end - - if firstItem then - table.insert(buffer, "nothing") - end - - if not outputBuffer then - return table.concat(buffer) - end -end - -function Player.getRewardChest(self, autocreate) - return self:getDepotChest(99, autocreate) -end - -function Player.inBossFight(self) - if not next(globalBosses) then - return false - end - - local playerGuid = self:getGuid() - for _, info in pairs(globalBosses) do - local stats = info[playerGuid] - if stats and stats.active then - return stats - end - end - return false -end - --- by https://otland.net/members/cbrm.25752/ with some modifications -function MonsterType.createLootItem(self, lootBlock, chance, lootTable) - local lootTable, itemCount = lootTable or {}, 0 - local randvalue = math.random(0, 100000) / (getConfigInfo("rateLoot") * chance) - if randvalue < lootBlock.chance then - if (ItemType(lootBlock.itemId):isStackable()) then - itemCount = randvalue % lootBlock.maxCount + 1 - else - itemCount = 1 - end - end - - while itemCount > 0 do - local n = math.min(itemCount, 100) - itemCount = itemCount - n - table.insert(lootTable, {lootBlock.itemId, n}) - end - - return lootTable -end - -function MonsterType.getBossReward(self, lootFactor, topScore) - local result = {} - if getConfigInfo("rateLoot") > 0 then - for _, lootBlock in pairs(self:getLoot()) do - if lootBlock.unique and not topScore then - --continue - else - self:createLootItem(lootBlock, lootFactor, result) - end - end - end - return result -end \ No newline at end of file diff --git a/data/lib/debugging/dump.lua b/data/lib/debugging/dump.lua new file mode 100644 index 0000000..4f9ccf3 --- /dev/null +++ b/data/lib/debugging/dump.lua @@ -0,0 +1,59 @@ +-- recursive dump function +function dumpLevel(input, level) + local indent = '' + + for i = 1, level do + indent = indent .. ' ' + end + + if type(input) == 'table' then + local str = '{ \n' + local lines = {} + + for k, v in pairs(input) do + if type(k) ~= 'number' then + k = '"' .. k .. '"' + end + + if type(v) == 'string' then + v = '"' .. v .. '"' + end + + table.insert(lines, indent .. ' [' .. k .. '] = ' .. dumpLevel(v, level + 1)) + end + return str .. table.concat(lines, ',\n') .. '\n' .. indent .. '}' + end + + return tostring(input) +end + +-- Return a string representation of input for debugging purposes +function dump(input) + return dumpLevel(input, 0) +end + +-- Call the dump function and print it to console +function pdump(input) + local dump_str = dump(input) + print(dump_str) + return dump_str +end + +-- Call the dump function with a title and print it beautifully to the console +function tdump(title, input) + local title_fill = '' + for i = 1, title:len() do + title_fill = title_fill .. '=' + end + + local header_str = '\n====' .. title_fill .. '====\n' + header_str = header_str .. '=== ' .. title .. ' ===\n' + header_str = header_str .. '====' .. title_fill .. '====\n' + + local dump_str = dump(input) + local footer_str = '\n====' .. title_fill .. '====\n' + + print(header_str .. dump_str .. footer_str) + + return dump_str +end diff --git a/data/lib/debugging/lua_version.lua b/data/lib/debugging/lua_version.lua new file mode 100644 index 0000000..18294c1 --- /dev/null +++ b/data/lib/debugging/lua_version.lua @@ -0,0 +1,5 @@ +if type(jit) == 'table' then + print('>> Using ' .. jit.version) --LuaJIT 2.0.2 +else + print('>> Using ' .. _VERSION) +end diff --git a/data/lib/lib.lua b/data/lib/lib.lua index d8389eb..1299f22 100644 --- a/data/lib/lib.lua +++ b/data/lib/lib.lua @@ -1,7 +1,13 @@ -- Core API functions implemented in Lua dofile('data/lib/core/core.lua') --- Custom -dofile('data/lib/custom/custom.lua') +-- Compatibility library for our old Lua API +-- dofile('data/lib/compat/compat.lua') + +-- Debugging helper function for Lua developers +dofile('data/lib/debugging/dump.lua') +dofile('data/lib/debugging/lua_version.lua') + +-- Custom LIB styller +dofile('data/lib/custom/compat.lua') dofile('data/lib/custom/storages.lua') -dofile('data/lib/custom/rewardboss.lua') diff --git a/data/migrations/1.lua b/data/migrations/1.lua index c3e6f22..eb4582f 100644 --- a/data/migrations/1.lua +++ b/data/migrations/1.lua @@ -1,5 +1,5 @@ function onUpdateDatabase() print("> Updating database to version 2 (market offers)") - db.query("CREATE TABLE `market_offers` (`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `player_id` INT NOT NULL, `sale` TINYINT(1) NOT NULL DEFAULT 0, `itemtype` INT UNSIGNED NOT NULL, `amount` SMALLINT UNSIGNED NOT NULL, `created` BIGINT UNSIGNED NOT NULL, `anonymous` TINYINT(1) NOT NULL DEFAULT 0, `price` INT UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (`id`), KEY(`sale`, `itemtype`), KEY(`created`), FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE) ENGINE = InnoDB") + db.query("CREATE TABLE `market_offers` (`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `player_id` INT NOT NULL, `sale` TINYINT NOT NULL DEFAULT 0, `itemtype` INT UNSIGNED NOT NULL, `amount` SMALLINT UNSIGNED NOT NULL, `created` BIGINT UNSIGNED NOT NULL, `anonymous` TINYINT(1) NOT NULL DEFAULT 0, `price` INT UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (`id`), KEY(`sale`, `itemtype`), KEY(`created`), FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE) ENGINE = InnoDB") return true end diff --git a/data/migrations/10.lua b/data/migrations/10.lua index 79efeca..d79547d 100644 --- a/data/migrations/10.lua +++ b/data/migrations/10.lua @@ -1,6 +1,6 @@ function onUpdateDatabase() print("> Updating database to version 11 (improved guild and players online structure)") - db.query("CREATE TABLE IF NOT EXISTS `guild_membership` (`player_id` int(11) NOT NULL, `guild_id` int(11) NOT NULL, `rank_id` int(11) NOT NULL, `nick` varchar(15) NOT NULL DEFAULT '', PRIMARY KEY (`player_id`), KEY `guild_id` (`guild_id`), KEY `rank_id` (`rank_id`)) ENGINE=InnoDB") + db.query("CREATE TABLE IF NOT EXISTS `guild_membership` (`player_id` int NOT NULL, `guild_id` int NOT NULL, `rank_id` int NOT NULL, `nick` varchar(15) NOT NULL DEFAULT '', PRIMARY KEY (`player_id`), KEY `guild_id` (`guild_id`), KEY `rank_id` (`rank_id`)) ENGINE=InnoDB") db.query("ALTER TABLE `guild_membership` ADD CONSTRAINT `guild_membership_ibfk_3` FOREIGN KEY (`rank_id`) REFERENCES `guild_ranks` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT `guild_membership_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT `guild_membership_ibfk_2` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`id`) ON DELETE CASCADE ON UPDATE CASCADE") db.query("ALTER TABLE `guild_invites` ADD PRIMARY KEY (`player_id`, `guild_id`)") db.query("ALTER TABLE `player_skills` ADD PRIMARY KEY (`player_id`, `skillid`)") @@ -23,6 +23,6 @@ function onUpdateDatabase() db.query("ALTER TABLE `players` DROP `rank_id`, DROP `guildnick`, DROP `direction`, DROP `loss_experience`, DROP `loss_mana`, DROP `loss_skills`, DROP `premend`, DROP `online`") db.query("DROP TRIGGER IF EXISTS `ondelete_guilds`") - db.query("CREATE TABLE IF NOT EXISTS `players_online` (`player_id` int(11) NOT NULL, PRIMARY KEY (`player_id`)) ENGINE=MEMORY") + db.query("CREATE TABLE IF NOT EXISTS `players_online` (`player_id` int NOT NULL, PRIMARY KEY (`player_id`)) ENGINE=MEMORY") return true end diff --git a/data/migrations/12.lua b/data/migrations/12.lua index 6e16858..9e16bf8 100644 --- a/data/migrations/12.lua +++ b/data/migrations/12.lua @@ -1,9 +1,9 @@ function onUpdateDatabase() print("> Updating database to version 13 (house bidding system and additional columns to other tables)") - db.query("ALTER TABLE `player_deaths` ADD `mostdamage_by` varchar(100) NOT NULL, ADD `mostdamage_is_player` tinyint(1) NOT NULL DEFAULT '0', ADD `unjustified` tinyint(1) NOT NULL DEFAULT '0', ADD `mostdamage_unjustified` tinyint(1) NOT NULL DEFAULT '0', ADD KEY `killed_by` (`killed_by`), ADD KEY `mostdamage_by` (`mostdamage_by`)") - db.query("ALTER TABLE `houses` ADD `name` varchar(255) NOT NULL, ADD `rent` int(11) NOT NULL DEFAULT '0', ADD `town_id` int(11) NOT NULL DEFAULT '0', ADD `bid` int(11) NOT NULL DEFAULT '0', ADD `bid_end` int(11) NOT NULL DEFAULT '0', ADD `last_bid` int(11) NOT NULL DEFAULT '0', ADD `highest_bidder` int(11) NOT NULL DEFAULT '0', ADD `size` int(11) NOT NULL DEFAULT '0', ADD `beds` int(11) NOT NULL DEFAULT '0', ADD KEY `owner` (`owner`), ADD KEY `town_id` (`town_id`)") - db.query("ALTER TABLE `players` ADD `onlinetime` int(11) NOT NULL DEFAULT '0', ADD `deletion` bigint(15) NOT NULL DEFAULT '0'") - db.query("ALTER TABLE `accounts` ADD `points` int(11) NOT NULL DEFAULT '0', ADD `creation` int(11) NOT NULL DEFAULT '0'") + db.query("ALTER TABLE `player_deaths` ADD `mostdamage_by` varchar(100) NOT NULL, ADD `mostdamage_is_player` tinyint NOT NULL DEFAULT '0', ADD `unjustified` tinyint NOT NULL DEFAULT '0', ADD `mostdamage_unjustified` tinyint NOT NULL DEFAULT '0', ADD KEY `killed_by` (`killed_by`), ADD KEY `mostdamage_by` (`mostdamage_by`)") + db.query("ALTER TABLE `houses` ADD `name` varchar(255) NOT NULL, ADD `rent` int NOT NULL DEFAULT '0', ADD `town_id` int NOT NULL DEFAULT '0', ADD `bid` int NOT NULL DEFAULT '0', ADD `bid_end` int NOT NULL DEFAULT '0', ADD `last_bid` int NOT NULL DEFAULT '0', ADD `highest_bidder` int NOT NULL DEFAULT '0', ADD `size` int NOT NULL DEFAULT '0', ADD `beds` int NOT NULL DEFAULT '0', ADD KEY `owner` (`owner`), ADD KEY `town_id` (`town_id`)") + db.query("ALTER TABLE `players` ADD `onlinetime` int NOT NULL DEFAULT '0', ADD `deletion` bigint NOT NULL DEFAULT '0'") + db.query("ALTER TABLE `accounts` ADD `points` int NOT NULL DEFAULT '0', ADD `creation` int NOT NULL DEFAULT '0'") return true end diff --git a/data/migrations/13.lua b/data/migrations/13.lua index 4cbd9ff..ea4f072 100644 --- a/data/migrations/13.lua +++ b/data/migrations/13.lua @@ -1,10 +1,10 @@ function onUpdateDatabase() print("> Updating database to version 14 (account_bans, ip_bans and player_bans)") - db.query("CREATE TABLE IF NOT EXISTS `account_bans` (`account_id` int(11) NOT NULL, `reason` varchar(255) NOT NULL, `banned_at` bigint(20) NOT NULL, `expires_at` bigint(20) NOT NULL, `banned_by` int(11) NOT NULL, PRIMARY KEY (`account_id`), KEY `banned_by` (`banned_by`), FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (`banned_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB") - db.query("CREATE TABLE IF NOT EXISTS `account_ban_history` (`account_id` int(11) NOT NULL, `reason` varchar(255) NOT NULL, `banned_at` bigint(20) NOT NULL, `expired_at` bigint(20) NOT NULL, `banned_by` int(11) NOT NULL, PRIMARY KEY (`account_id`), KEY `banned_by` (`banned_by`), FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (`banned_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB") - db.query("CREATE TABLE IF NOT EXISTS `ip_bans` (`ip` int(10) unsigned NOT NULL, `reason` varchar(255) NOT NULL, `banned_at` bigint(20) NOT NULL, `expires_at` bigint(20) NOT NULL, `banned_by` int(11) NOT NULL, PRIMARY KEY (`ip`), KEY `banned_by` (`banned_by`), FOREIGN KEY (`banned_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB") - db.query("CREATE TABLE IF NOT EXISTS `player_namelocks` (`player_id` int(11) NOT NULL, `reason` varchar(255) NOT NULL, `namelocked_at` bigint(20) NOT NULL, `namelocked_by` int(11) NOT NULL, PRIMARY KEY (`player_id`), KEY `namelocked_by` (`namelocked_by`), FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (`namelocked_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB") + db.query("CREATE TABLE IF NOT EXISTS `account_bans` (`account_id` int NOT NULL, `reason` varchar(255) NOT NULL, `banned_at` bigint NOT NULL, `expires_at` bigint NOT NULL, `banned_by` int NOT NULL, PRIMARY KEY (`account_id`), KEY `banned_by` (`banned_by`), FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (`banned_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB") + db.query("CREATE TABLE IF NOT EXISTS `account_ban_history` (`account_id` int NOT NULL, `reason` varchar(255) NOT NULL, `banned_at` bigint NOT NULL, `expired_at` bigint NOT NULL, `banned_by` int NOT NULL, PRIMARY KEY (`account_id`), KEY `banned_by` (`banned_by`), FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (`banned_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB") + db.query("CREATE TABLE IF NOT EXISTS `ip_bans` (`ip` int unsigned NOT NULL, `reason` varchar(255) NOT NULL, `banned_at` bigint NOT NULL, `expires_at` bigint NOT NULL, `banned_by` int NOT NULL, PRIMARY KEY (`ip`), KEY `banned_by` (`banned_by`), FOREIGN KEY (`banned_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB") + db.query("CREATE TABLE IF NOT EXISTS `player_namelocks` (`player_id` int NOT NULL, `reason` varchar(255) NOT NULL, `namelocked_at` bigint NOT NULL, `namelocked_by` int NOT NULL, PRIMARY KEY (`player_id`), KEY `namelocked_by` (`namelocked_by`), FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (`namelocked_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB") local resultId = db.storeQuery("SELECT `player`, `time` FROM `bans` WHERE `type` = 2") if resultId ~= false then diff --git a/data/migrations/15.lua b/data/migrations/15.lua index f04796b..475f043 100644 --- a/data/migrations/15.lua +++ b/data/migrations/15.lua @@ -1,6 +1,6 @@ function onUpdateDatabase() print("> Updating database to version 16 (moving skills into players table)") - db.query("ALTER TABLE `players` ADD `skill_fist` int(10) unsigned NOT NULL DEFAULT 10, ADD `skill_fist_tries` bigint(20) unsigned NOT NULL DEFAULT 0, ADD `skill_club` int(10) unsigned NOT NULL DEFAULT 10, ADD `skill_club_tries` bigint(20) unsigned NOT NULL DEFAULT 0, ADD `skill_sword` int(10) unsigned NOT NULL DEFAULT 10, ADD `skill_sword_tries` bigint(20) unsigned NOT NULL DEFAULT 0, ADD `skill_axe` int(10) unsigned NOT NULL DEFAULT 10, ADD `skill_axe_tries` bigint(20) unsigned NOT NULL DEFAULT 0, ADD `skill_dist` int(10) unsigned NOT NULL DEFAULT 10, ADD `skill_dist_tries` bigint(20) unsigned NOT NULL DEFAULT 0, ADD `skill_shielding` int(10) unsigned NOT NULL DEFAULT 10, ADD `skill_shielding_tries` bigint(20) unsigned NOT NULL DEFAULT 0, ADD `skill_fishing` int(10) unsigned NOT NULL DEFAULT 10, ADD `skill_fishing_tries` bigint(20) unsigned NOT NULL DEFAULT 0") + db.query("ALTER TABLE `players` ADD `skill_fist` int unsigned NOT NULL DEFAULT 10, ADD `skill_fist_tries` bigint unsigned NOT NULL DEFAULT 0, ADD `skill_club` int unsigned NOT NULL DEFAULT 10, ADD `skill_club_tries` bigint unsigned NOT NULL DEFAULT 0, ADD `skill_sword` int unsigned NOT NULL DEFAULT 10, ADD `skill_sword_tries` bigint unsigned NOT NULL DEFAULT 0, ADD `skill_axe` int unsigned NOT NULL DEFAULT 10, ADD `skill_axe_tries` bigint unsigned NOT NULL DEFAULT 0, ADD `skill_dist` int unsigned NOT NULL DEFAULT 10, ADD `skill_dist_tries` bigint unsigned NOT NULL DEFAULT 0, ADD `skill_shielding` int unsigned NOT NULL DEFAULT 10, ADD `skill_shielding_tries` bigint unsigned NOT NULL DEFAULT 0, ADD `skill_fishing` int unsigned NOT NULL DEFAULT 10, ADD `skill_fishing_tries` bigint unsigned NOT NULL DEFAULT 0") db.query("UPDATE `players` SET `skill_fist` = (SELECT `value` FROM `player_skills` WHERE `player_id` = `id` AND `skillid` = 0), `skill_fist_tries` = (SELECT `count` FROM `player_skills` WHERE `player_id` = `id` AND `skillid` = 0), `skill_club` = (SELECT `value` FROM `player_skills` WHERE `player_id` = `id` AND `skillid` = 1), `skill_club_tries` = (SELECT `count` FROM `player_skills` WHERE `player_id` = `id` AND `skillid` = 1), `skill_sword` = (SELECT `value` FROM `player_skills` WHERE `player_id` = `id` AND `skillid` = 2), `skill_sword_tries` = (SELECT `count` FROM `player_skills` WHERE `player_id` = `id` AND `skillid` = 2), `skill_axe` = (SELECT `value` FROM `player_skills` WHERE `player_id` = `id` AND `skillid` = 3), `skill_axe_tries` = (SELECT `count` FROM `player_skills` WHERE `player_id` = `id` AND `skillid` = 3), `skill_dist` = (SELECT `value` FROM `player_skills` WHERE `player_id` = `id` AND `skillid` = 4), `skill_dist_tries` = (SELECT `count` FROM `player_skills` WHERE `player_id` = `id` AND `skillid` = 4), `skill_shielding` = (SELECT `value` FROM `player_skills` WHERE `player_id` = `id` AND `skillid` = 5), `skill_shielding_tries` = (SELECT `count` FROM `player_skills` WHERE `player_id` = `id` AND `skillid` = 5), `skill_fishing` = (SELECT `value` FROM `player_skills` WHERE `player_id` = `id` AND `skillid` = 6), `skill_fishing_tries` = (SELECT `count` FROM `player_skills` WHERE `player_id` = `id` AND `skillid` = 6)") db.query("DROP TRIGGER `oncreate_players`") db.query("DROP TABLE `player_skills`") diff --git a/data/migrations/19.lua b/data/migrations/19.lua index 1e7e1ae..e19007c 100644 --- a/data/migrations/19.lua +++ b/data/migrations/19.lua @@ -1,5 +1,5 @@ function onUpdateDatabase() print("> Updating database to version 20 (setting default cap to 400)") - db.query("ALTER TABLE `players` CHANGE `cap` `cap` int(11) NOT NULL DEFAULT '400'") + db.query("ALTER TABLE `players` CHANGE `cap` `cap` int NOT NULL DEFAULT '400'") return true end diff --git a/data/migrations/20.lua b/data/migrations/20.lua index 135905d..0e35de8 100644 --- a/data/migrations/20.lua +++ b/data/migrations/20.lua @@ -1,6 +1,6 @@ function onUpdateDatabase() print("> Updating database to version 21 (store towns in database)") - db.query("CREATE TABLE IF NOT EXISTS `towns` (`id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `posx` int(11) NOT NULL DEFAULT '0', `posy` int(11) NOT NULL DEFAULT '0', `posz` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`)) ENGINE=InnoDB") + db.query("CREATE TABLE IF NOT EXISTS `towns` (`id` int NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `posx` int NOT NULL DEFAULT '0', `posy` int NOT NULL DEFAULT '0', `posz` int NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`)) ENGINE=InnoDB") return true end diff --git a/data/migrations/22.lua b/data/migrations/22.lua index 703aa0b..cd27a7d 100644 --- a/data/migrations/22.lua +++ b/data/migrations/22.lua @@ -1,5 +1,5 @@ function onUpdateDatabase() print("> Updating database to version 23 (Fix skulltime)") - db.query("ALTER TABLE players MODIFY skulltime BIGINT(20) NOT NULL DEFAULT 0") + db.query("ALTER TABLE players MODIFY skulltime BIGINT NOT NULL DEFAULT 0") return true end diff --git a/data/migrations/23.lua b/data/migrations/23.lua index 27daa57..7c45065 100644 --- a/data/migrations/23.lua +++ b/data/migrations/23.lua @@ -1,5 +1,5 @@ function onUpdateDatabase() print("> Updating database to version 24 (Add player direction)") - db.query("ALTER TABLE `players` ADD `direction` int(1) unsigned NOT NULL DEFAULT 2") + db.query("ALTER TABLE `players` ADD `direction` int unsigned NOT NULL DEFAULT 2") return true end diff --git a/data/migrations/24.lua b/data/migrations/24.lua index 57e929f..ccc43a4 100644 --- a/data/migrations/24.lua +++ b/data/migrations/24.lua @@ -1,7 +1,5 @@ function onUpdateDatabase() - print("> Updating database to version 24 (OTX Migrations)") - --db.query("UPDATE `player_depotitems` SET `pid` = 17 WHERE `pid` = 0") - --db.query("UPDATE `player_depotitems` SET `pid` = 17 WHERE `pid` > 17") - db.query("CREATE TABLE IF NOT EXISTS `player_rewards` ( `player_id` int(11) NOT NULL, `sid` int(11) NOT NULL, `pid` int(11) NOT NULL DEFAULT '0', `itemtype` smallint(6) NOT NULL, `count` smallint(5) NOT NULL DEFAULT '0', `attributes` blob NOT NULL, UNIQUE KEY `player_id_2` (`player_id`, `sid`), FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE ) ENGINE=InnoDB;") + print("> Updating database to version 25 (Optimize player direction 4 -> 1 byte)") + db.query("ALTER TABLE `players` MODIFY COLUMN `direction` tinyint unsigned NOT NULL DEFAULT 2") return true end diff --git a/data/migrations/25.lua b/data/migrations/25.lua index d0ffd9c..bd772dc 100644 --- a/data/migrations/25.lua +++ b/data/migrations/25.lua @@ -1,3 +1,16 @@ function onUpdateDatabase() - return false + print("> Updating database to version 25 (Store inbox changes)") + db.query([[ + CREATE TABLE IF NOT EXISTS `player_storeinboxitems` ( + `player_id` int(11) NOT NULL, + `sid` int(11) NOT NULL, + `pid` int(11) NOT NULL DEFAULT '0', + `itemtype` smallint(6) NOT NULL, + `count` smallint(5) NOT NULL DEFAULT '0', + `attributes` blob NOT NULL, + UNIQUE KEY `player_id_2` (`player_id`, `sid`), + FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; + ]]) + return true end diff --git a/data/migrations/26.lua b/data/migrations/26.lua new file mode 100644 index 0000000..f726068 --- /dev/null +++ b/data/migrations/26.lua @@ -0,0 +1,7 @@ +function onUpdateDatabase() + print("> Updating database to version 26 (Better premium time handling)") + db.query("ALTER TABLE `accounts` DROP COLUMN `lastday`") + db.query("ALTER TABLE `accounts` CHANGE COLUMN `premdays` `premium_ends_at` int unsigned NOT NULL DEFAULT 0"); + db.query("UPDATE `accounts` SET `premium_ends_at` = `premium_ends_at` * 86400 + " .. os.time()) + return true +end diff --git a/data/migrations/27.lua b/data/migrations/27.lua new file mode 100644 index 0000000..095fbb8 --- /dev/null +++ b/data/migrations/27.lua @@ -0,0 +1,7 @@ +function onUpdateDatabase() + print("> Updating database to version 27 (data type mismatch)") + db.query("ALTER TABLE `players` CHANGE `manaspent` `manaspent` bigint unsigned NOT NULL DEFAULT '0'"); + db.query("ALTER TABLE `players` CHANGE `experience` `experience` bigint unsigned NOT NULL DEFAULT '0'"); + db.query("ALTER TABLE `players` CHANGE `onlinetime` `onlinetime` bigint NOT NULL DEFAULT '0'"); + return true +end diff --git a/data/migrations/28.lua b/data/migrations/28.lua new file mode 100644 index 0000000..52ad149 --- /dev/null +++ b/data/migrations/28.lua @@ -0,0 +1,10 @@ +function onUpdateDatabase() + print("> Updating database to version 28 (data type mismatch 2)") + db.query("ALTER TABLE `market_history` CHANGE `itemtype` `itemtype` smallint unsigned NOT NULL"); + db.query("ALTER TABLE `market_offers` CHANGE `itemtype` `itemtype` smallint unsigned NOT NULL"); + db.query("ALTER TABLE `player_depotitems` CHANGE `itemtype` `itemtype` smallint unsigned NOT NULL"); + db.query("ALTER TABLE `player_inboxitems` CHANGE `itemtype` `itemtype` smallint unsigned NOT NULL"); + db.query("ALTER TABLE `player_storeinboxitems` CHANGE `itemtype` `itemtype` smallint unsigned NOT NULL"); + db.query("ALTER TABLE `player_items` CHANGE `itemtype` `itemtype` smallint unsigned NOT NULL"); + return true +end diff --git a/data/migrations/29.lua b/data/migrations/29.lua new file mode 100644 index 0000000..6a2554d --- /dev/null +++ b/data/migrations/29.lua @@ -0,0 +1,13 @@ +function onUpdateDatabase() + print("> Updating database to version 29 (account storages)") + db.query([[ + CREATE TABLE IF NOT EXISTS `account_storage` ( + `account_id` int NOT NULL, + `key` int unsigned NOT NULL, + `value` int NOT NULL, + PRIMARY KEY (`account_id`, `key`), + FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; + ]]) + return true +end diff --git a/data/migrations/3.lua b/data/migrations/3.lua index c93e34d..fb7b87e 100644 --- a/data/migrations/3.lua +++ b/data/migrations/3.lua @@ -1,5 +1,5 @@ function onUpdateDatabase() print("> Updating database to version 4 (market history)") - db.query("CREATE TABLE `market_history` (`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `player_id` INT NOT NULL, `sale` TINYINT(1) NOT NULL DEFAULT 0, `itemtype` INT UNSIGNED NOT NULL, `amount` SMALLINT UNSIGNED NOT NULL, `price` INT UNSIGNED NOT NULL DEFAULT 0, `expires_at` BIGINT UNSIGNED NOT NULL, `inserted` BIGINT UNSIGNED NOT NULL, `state` TINYINT(1) UNSIGNED NOT NULL, PRIMARY KEY(`id`), KEY(`player_id`, `sale`), FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE) ENGINE = InnoDB") + db.query("CREATE TABLE `market_history` (`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `player_id` INT NOT NULL, `sale` TINYINT NOT NULL DEFAULT 0, `itemtype` INT UNSIGNED NOT NULL, `amount` SMALLINT UNSIGNED NOT NULL, `price` INT UNSIGNED NOT NULL DEFAULT 0, `expires_at` BIGINT UNSIGNED NOT NULL, `inserted` BIGINT UNSIGNED NOT NULL, `state` TINYINT UNSIGNED NOT NULL, PRIMARY KEY(`id`), KEY(`player_id`, `sale`), FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE) ENGINE = InnoDB") return true end diff --git a/data/migrations/-1.lua b/data/migrations/30.lua similarity index 91% rename from data/migrations/-1.lua rename to data/migrations/30.lua index 53778c6..d0ffd9c 100644 --- a/data/migrations/-1.lua +++ b/data/migrations/30.lua @@ -1,3 +1,3 @@ function onUpdateDatabase() return false -end \ No newline at end of file +end diff --git a/data/migrations/4.lua b/data/migrations/4.lua index 069a643..5d7814b 100644 --- a/data/migrations/4.lua +++ b/data/migrations/4.lua @@ -1,7 +1,7 @@ function onUpdateDatabase() print("> Updating database to version 5 (black skull & guild wars)") - db.query("ALTER TABLE `players` CHANGE `redskull` `skull` TINYINT(1) NOT NULL DEFAULT '0', CHANGE `redskulltime` `skulltime` INT(11) NOT NULL DEFAULT '0'") - db.query("CREATE TABLE IF NOT EXISTS `guild_wars` ( `id` int(11) NOT NULL AUTO_INCREMENT, `guild1` int(11) NOT NULL DEFAULT '0', `guild2` int(11) NOT NULL DEFAULT '0', `name1` varchar(255) NOT NULL, `name2` varchar(255) NOT NULL, `status` tinyint(2) NOT NULL DEFAULT '0', `started` bigint(15) NOT NULL DEFAULT '0', `ended` bigint(15) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `guild1` (`guild1`), KEY `guild2` (`guild2`)) ENGINE=InnoDB") - db.query("CREATE TABLE IF NOT EXISTS `guildwar_kills` (`id` int(11) NOT NULL AUTO_INCREMENT, `killer` varchar(50) NOT NULL, `target` varchar(50) NOT NULL, `killerguild` int(11) NOT NULL DEFAULT '0', `targetguild` int(11) NOT NULL DEFAULT '0', `warid` int(11) NOT NULL DEFAULT '0', `time` bigint(15) NOT NULL, PRIMARY KEY (`id`), KEY `warid` (`warid`), FOREIGN KEY (`warid`) REFERENCES `guild_wars`(`id`) ON DELETE CASCADE) ENGINE=InnoDB") + db.query("ALTER TABLE `players` CHANGE `redskull` `skull` TINYINT NOT NULL DEFAULT '0', CHANGE `redskulltime` `skulltime` INT NOT NULL DEFAULT '0'") + db.query("CREATE TABLE IF NOT EXISTS `guild_wars` ( `id` int NOT NULL AUTO_INCREMENT, `guild1` int NOT NULL DEFAULT '0', `guild2` int NOT NULL DEFAULT '0', `name1` varchar(255) NOT NULL, `name2` varchar(255) NOT NULL, `status` tinyint NOT NULL DEFAULT '0', `started` bigint NOT NULL DEFAULT '0', `ended` bigint NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `guild1` (`guild1`), KEY `guild2` (`guild2`)) ENGINE=InnoDB") + db.query("CREATE TABLE IF NOT EXISTS `guildwar_kills` (`id` int NOT NULL AUTO_INCREMENT, `killer` varchar(50) NOT NULL, `target` varchar(50) NOT NULL, `killerguild` int NOT NULL DEFAULT '0', `targetguild` int NOT NULL DEFAULT '0', `warid` int NOT NULL DEFAULT '0', `time` bigint NOT NULL, PRIMARY KEY (`id`), KEY `warid` (`warid`), FOREIGN KEY (`warid`) REFERENCES `guild_wars`(`id`) ON DELETE CASCADE) ENGINE=InnoDB") return true end diff --git a/data/migrations/8.lua b/data/migrations/8.lua index 10aa660..166a224 100644 --- a/data/migrations/8.lua +++ b/data/migrations/8.lua @@ -1,6 +1,6 @@ function onUpdateDatabase() print("> Updating database to version 9 (global inbox)") - db.query("CREATE TABLE IF NOT EXISTS `player_inboxitems` (`player_id` int(11) NOT NULL, `sid` int(11) NOT NULL, `pid` int(11) NOT NULL DEFAULT '0', `itemtype` smallint(6) NOT NULL, `count` smallint(5) NOT NULL DEFAULT '0', `attributes` blob NOT NULL, UNIQUE KEY `player_id_2` (`player_id`,`sid`), KEY `player_id` (`player_id`), FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=latin1") + db.query("CREATE TABLE IF NOT EXISTS `player_inboxitems` (`player_id` int NOT NULL, `sid` int NOT NULL, `pid` int NOT NULL DEFAULT '0', `itemtype` smallint NOT NULL, `count` smallint NOT NULL DEFAULT '0', `attributes` blob NOT NULL, UNIQUE KEY `player_id_2` (`player_id`,`sid`), KEY `player_id` (`player_id`), FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=latin1") -- Delete "market" item db.query("DELETE FROM `player_depotitems` WHERE `itemtype` = 14405") diff --git a/data/movements/movements.xml b/data/movements/movements.xml index 8051394..b4dbe86 100644 --- a/data/movements/movements.xml +++ b/data/movements/movements.xml @@ -1,31 +1,31 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - + + + + @@ -49,7 +49,7 @@ - + @@ -58,13 +58,12 @@ + - + - - @@ -299,10 +298,10 @@ - - + + @@ -366,6 +365,8 @@ + + @@ -376,8 +377,6 @@ - - @@ -403,46 +402,49 @@ + + + + - - - - + + - - + - + + + + + + + + + - - - - - - @@ -450,8 +452,19 @@ + + + + + + + + + + + @@ -580,6 +593,11 @@ + + + + + @@ -752,11 +770,6 @@ - - - - - @@ -783,6 +796,11 @@ + + + + + @@ -790,34 +808,29 @@ - - - - - - + - - + + - - + + - + @@ -869,31 +882,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - @@ -929,6 +917,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + @@ -936,6 +949,11 @@ + + + + + @@ -943,9 +961,4 @@ - - - - - diff --git a/data/movements/scripts/citizen.lua b/data/movements/scripts/citizen.lua index b32c290..de44d19 100644 --- a/data/movements/scripts/citizen.lua +++ b/data/movements/scripts/citizen.lua @@ -1,28 +1,14 @@ -local config = { - [9601] = 2, - [9617] = 3 -} - function onStepIn(creature, item, position, fromPosition) - local player = creature:getPlayer() - if not player then - return true + if item.actionid > actionIds.citizenship and item.actionid < actionIds.citizenshipLast then + if not creature:isPlayer() then + return false + end + local town = Town(item.actionid - actionIds.citizenship) + if not town then + return false + end + creature:setTown(town) + creature:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are now a citizen of " .. town:getName() .. ".") end - - local townId = config[item.uid] - if not townId then - return true - end - - local town = Town(townId) - if not town then - return true - end - - player:setTown(town) - player:teleportTo(town:getTemplePosition()) - player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, 'You are now a citizen of ' .. town:getName() .. '.') - return true end diff --git a/data/movements/scripts/boss_room_enter.lua b/data/movements/scripts/custom/boss_room_enter.lua similarity index 100% rename from data/movements/scripts/boss_room_enter.lua rename to data/movements/scripts/custom/boss_room_enter.lua diff --git a/data/movements/scripts/boss_room_leave.lua b/data/movements/scripts/custom/boss_room_leave.lua similarity index 100% rename from data/movements/scripts/boss_room_leave.lua rename to data/movements/scripts/custom/boss_room_leave.lua diff --git a/data/movements/scripts/custom/citizen.lua b/data/movements/scripts/custom/citizen.lua new file mode 100644 index 0000000..b32c290 --- /dev/null +++ b/data/movements/scripts/custom/citizen.lua @@ -0,0 +1,28 @@ +local config = { + [9601] = 2, + [9617] = 3 +} + +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + local townId = config[item.uid] + if not townId then + return true + end + + local town = Town(townId) + if not town then + return true + end + + player:setTown(town) + player:teleportTo(town:getTemplePosition()) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, 'You are now a citizen of ' .. town:getName() .. '.') + + return true +end diff --git a/data/movements/scripts/events/battlefield.lua b/data/movements/scripts/custom/events/battlefield.lua similarity index 100% rename from data/movements/scripts/events/battlefield.lua rename to data/movements/scripts/custom/events/battlefield.lua diff --git a/data/movements/scripts/events/duca.lua b/data/movements/scripts/custom/events/duca.lua similarity index 100% rename from data/movements/scripts/events/duca.lua rename to data/movements/scripts/custom/events/duca.lua diff --git a/data/movements/scripts/events/zombie.lua b/data/movements/scripts/custom/events/zombie.lua similarity index 100% rename from data/movements/scripts/events/zombie.lua rename to data/movements/scripts/custom/events/zombie.lua diff --git a/data/movements/scripts/home_teleport.lua b/data/movements/scripts/custom/home_teleport.lua similarity index 100% rename from data/movements/scripts/home_teleport.lua rename to data/movements/scripts/custom/home_teleport.lua diff --git a/data/movements/scripts/custom/level_door.lua b/data/movements/scripts/custom/level_door.lua new file mode 100644 index 0000000..4eb134d --- /dev/null +++ b/data/movements/scripts/custom/level_door.lua @@ -0,0 +1,13 @@ +function onStepIn(creature, item, position, fromPosition) + if not creature:isPlayer() then + return false + end + + local x = item.actionid - 1000 + if creature:getLevel() < x then + creature:sendTextMessage(MESSAGE_INFO_DESCR, "Required level "..x.." to pass this door.") + creature:teleportTo(fromPosition, true) + return false + end + return true +end diff --git a/data/movements/scripts/premium_teleport.lua b/data/movements/scripts/custom/premium_teleport.lua similarity index 100% rename from data/movements/scripts/premium_teleport.lua rename to data/movements/scripts/custom/premium_teleport.lua diff --git a/data/movements/scripts/premium_tile.lua b/data/movements/scripts/custom/premium_tile.lua similarity index 100% rename from data/movements/scripts/premium_tile.lua rename to data/movements/scripts/custom/premium_tile.lua diff --git a/data/movements/scripts/pvp_arena.lua b/data/movements/scripts/custom/pvp_arena.lua similarity index 100% rename from data/movements/scripts/pvp_arena.lua rename to data/movements/scripts/custom/pvp_arena.lua diff --git a/data/movements/scripts/quests/demonOakEntrance.lua b/data/movements/scripts/custom/quests/demonOakEntrance.lua similarity index 100% rename from data/movements/scripts/quests/demonOakEntrance.lua rename to data/movements/scripts/custom/quests/demonOakEntrance.lua diff --git a/data/movements/scripts/quests/demonOakTeleport.lua b/data/movements/scripts/custom/quests/demonOakTeleport.lua similarity index 100% rename from data/movements/scripts/quests/demonOakTeleport.lua rename to data/movements/scripts/custom/quests/demonOakTeleport.lua diff --git a/data/movements/scripts/quests/hotaQuestTeleport.lua b/data/movements/scripts/custom/quests/hotaQuestTeleport.lua similarity index 100% rename from data/movements/scripts/quests/hotaQuestTeleport.lua rename to data/movements/scripts/custom/quests/hotaQuestTeleport.lua diff --git a/data/movements/scripts/quests/inquisitionQuestOutfit.lua b/data/movements/scripts/custom/quests/inquisitionQuestOutfit.lua similarity index 100% rename from data/movements/scripts/quests/inquisitionQuestOutfit.lua rename to data/movements/scripts/custom/quests/inquisitionQuestOutfit.lua diff --git a/data/movements/scripts/quests/inquisitionQuestTeleport.lua b/data/movements/scripts/custom/quests/inquisitionQuestTeleport.lua similarity index 100% rename from data/movements/scripts/quests/inquisitionQuestTeleport.lua rename to data/movements/scripts/custom/quests/inquisitionQuestTeleport.lua diff --git a/data/movements/scripts/quests/poi_thrones.lua b/data/movements/scripts/custom/quests/poi_thrones.lua similarity index 100% rename from data/movements/scripts/quests/poi_thrones.lua rename to data/movements/scripts/custom/quests/poi_thrones.lua diff --git a/data/movements/scripts/teleports_room.lua b/data/movements/scripts/custom/teleports_room.lua similarity index 100% rename from data/movements/scripts/teleports_room.lua rename to data/movements/scripts/custom/teleports_room.lua diff --git a/data/movements/scripts/temple_teleport.lua b/data/movements/scripts/custom/temple_teleport.lua similarity index 100% rename from data/movements/scripts/temple_teleport.lua rename to data/movements/scripts/custom/temple_teleport.lua diff --git a/data/movements/scripts/trainer_enter.lua b/data/movements/scripts/custom/trainer_enter.lua similarity index 100% rename from data/movements/scripts/trainer_enter.lua rename to data/movements/scripts/custom/trainer_enter.lua diff --git a/data/movements/scripts/trainer_leave.lua b/data/movements/scripts/custom/trainer_leave.lua similarity index 100% rename from data/movements/scripts/trainer_leave.lua rename to data/movements/scripts/custom/trainer_leave.lua diff --git a/data/movements/scripts/drowning.lua b/data/movements/scripts/drowning.lua index 8f6525f..18445d3 100644 --- a/data/movements/scripts/drowning.lua +++ b/data/movements/scripts/drowning.lua @@ -5,16 +5,14 @@ condition:setParameter(CONDITION_PARAM_TICKINTERVAL, 2000) function onStepIn(creature, item, position, fromPosition) if creature:isPlayer() then - if math.random(1, 10) == 1 then - position:sendMagicEffect(CONST_ME_BUBBLES) - end creature:addCondition(condition) + creature:addAchievementProgress("Deep Sea Diver", 1000000) end return true end function onStepOut(creature, item, position, fromPosition) - if not creature:isPlayer() then + if creature:isPlayer() then creature:removeCondition(CONDITION_DROWN) end return true diff --git a/data/movements/scripts/level_door.lua b/data/movements/scripts/level_door.lua index 4eb134d..a6e938b 100644 --- a/data/movements/scripts/level_door.lua +++ b/data/movements/scripts/level_door.lua @@ -3,9 +3,8 @@ function onStepIn(creature, item, position, fromPosition) return false end - local x = item.actionid - 1000 - if creature:getLevel() < x then - creature:sendTextMessage(MESSAGE_INFO_DESCR, "Required level "..x.." to pass this door.") + if creature:getLevel() < item.actionid - actionIds.levelDoor then + creature:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Only the worthy may pass.") creature:teleportTo(fromPosition, true) return false end diff --git a/data/movements/scripts/quest_door.lua b/data/movements/scripts/quest_door.lua index fc2c7e7..455795d 100644 --- a/data/movements/scripts/quest_door.lua +++ b/data/movements/scripts/quest_door.lua @@ -4,7 +4,7 @@ function onStepIn(creature, item, position, fromPosition) end if creature:getStorageValue(item.actionid) == -1 then - creature:sendTextMessage(MESSAGE_INFO_DESCR, "The door seems to be sealed against unwanted intruders.") + creature:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The door seems to be sealed against unwanted intruders.") creature:teleportTo(fromPosition, true) return false end diff --git a/data/movements/scripts/snow.lua b/data/movements/scripts/snow.lua index 8a07bbf..cf0f6c1 100644 --- a/data/movements/scripts/snow.lua +++ b/data/movements/scripts/snow.lua @@ -1,5 +1,5 @@ function onStepOut(creature, item, position, fromPosition) - if creature:isPlayer() and creature:isInGhostMode() then + if not creature:isPlayer() or creature:isInGhostMode() then return true end @@ -8,6 +8,7 @@ function onStepOut(creature, item, position, fromPosition) else item:transform(item.itemid + 15) end + creature:addAchievementProgress("Snowbunny", 10000) item:decay() return true end diff --git a/data/movements/scripts/swimming.lua b/data/movements/scripts/swimming.lua index 8964f44..93d4985 100644 --- a/data/movements/scripts/swimming.lua +++ b/data/movements/scripts/swimming.lua @@ -2,11 +2,21 @@ local condition = Condition(CONDITION_OUTFIT) condition:setOutfit({lookType = 267}) condition:setTicks(-1) +local conditions = { + CONDITION_POISON, CONDITION_FIRE, CONDITION_ENERGY, + CONDITION_PARALYZE, CONDITION_DRUNK, CONDITION_DROWN, + CONDITION_FREEZING, CONDITION_DAZZLED, CONDITION_CURSED, + CONDITION_BLEEDING +} + function onStepIn(creature, item, position, fromPosition) if not creature:isPlayer() then return false end - + for i = 1, #conditions do + creature:removeCondition(conditions[i]) + end + creature:addAchievementProgress("Waverider", 100000) creature:addCondition(condition) return true end diff --git a/data/movements/scripts/tiles.lua b/data/movements/scripts/tiles.lua index 2e00fd2..c9c0ae0 100644 --- a/data/movements/scripts/tiles.lua +++ b/data/movements/scripts/tiles.lua @@ -1,6 +1,5 @@ local increasing = {[416] = 417, [426] = 425, [446] = 447, [3216] = 3217, [3202] = 3215, [11062] = 11063} local decreasing = {[417] = 416, [425] = 426, [447] = 446, [3217] = 3216, [3215] = 3202, [11063] = 11062} -local maxLevel = 1000 function onStepIn(creature, item, position, fromPosition) if not increasing[item.itemid] then @@ -13,11 +12,11 @@ function onStepIn(creature, item, position, fromPosition) item:transform(increasing[item.itemid]) - if item.actionid >= 1000 and item.actionid - 1000 <= maxLevel then - if creature:getLevel() < item.actionid - 1000 then + if item.actionid >= actionIds.levelDoor then + if creature:getLevel() < item.actionid - actionIds.levelDoor then creature:teleportTo(fromPosition, false) position:sendMagicEffect(CONST_ME_MAGIC_BLUE) - creature:sendTextMessage(MESSAGE_INFO_DESCR, "The tile seems to be protected against unwanted intruders.") + creature:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The tile seems to be protected against unwanted intruders.") end return true end @@ -29,6 +28,7 @@ function onStepIn(creature, item, position, fromPosition) if depotItem then local depotItems = creature:getDepotChest(getDepotId(depotItem:getUniqueId()), true):getItemHoldingCount() creature:sendTextMessage(MESSAGE_STATUS_DEFAULT, "Your depot contains " .. depotItems .. " item" .. (depotItems > 1 and "s." or ".")) + creature:addAchievementProgress("Safely Stored Away", 1000) return true end end @@ -36,7 +36,7 @@ function onStepIn(creature, item, position, fromPosition) if item.actionid ~= 0 and creature:getStorageValue(item.actionid) <= 0 then creature:teleportTo(fromPosition, false) position:sendMagicEffect(CONST_ME_MAGIC_BLUE) - creature:sendTextMessage(MESSAGE_INFO_DESCR, "The tile seems to be protected against unwanted intruders.") + creature:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The tile seems to be protected against unwanted intruders.") return true end return true diff --git a/data/movements/scripts/trap.lua b/data/movements/scripts/trap.lua index b511e35..797cbfd 100644 --- a/data/movements/scripts/trap.lua +++ b/data/movements/scripts/trap.lua @@ -1,8 +1,20 @@ local traps = { - [1510] = {transformTo = 1511, damage = {-50, -100}}, - [1513] = {damage = {-50, -100}}, - [2579] = {transformTo = 2578, damage = {-15, -30}, ignorePlayer = false}, - [4208] = {transformTo = 4209, damage = {-15, -30}, type = COMBAT_EARTHDAMAGE} + [1510] = { -- strange slits + transformTo = 1511, + damage = {-60, -60} + }, + [1513] = { -- spikes + damage = {-60, -60} + }, + [2579] = { -- trap + transformTo = 2578, + damage = {-30, -30} + }, + [4208] = { -- jungle maw + transformTo = 4209, + damage = {-30, -30}, + type = COMBAT_EARTHDAMAGE + } } function onStepIn(creature, item, position, fromPosition) @@ -11,15 +23,13 @@ function onStepIn(creature, item, position, fromPosition) return true end - if trap.transformTo then - item:transform(trap.transformTo) + if creature:isMonster() or creature:isPlayer() then + doTargetCombat(0, creature, trap.type or COMBAT_PHYSICALDAMAGE, trap.damage[1], trap.damage[2], CONST_ME_NONE, true, false, false) end - if trap.ignorePlayer and creature:isPlayer() then - return true + if trap.transformTo then + item:transform(trap.transformTo) end - - doTargetCombatHealth(0, creature, trap.type or COMBAT_PHYSICALDAMAGE, trap.damage[1], trap.damage[2], CONST_ME_NONE) return true end diff --git a/data/movements/scripts/walkback.lua b/data/movements/scripts/walkback.lua index c97f7b5..30b00f2 100644 --- a/data/movements/scripts/walkback.lua +++ b/data/movements/scripts/walkback.lua @@ -1,41 +1,5 @@ -local function isQuestChest(item) - local itemid = item:getId() - if itemid == 1738 or itemid == 1740 or (itemid >= 1746 and itemid <= 1749) or (itemid >= 12664 and itemid <= 12665) or (itemid >= 12796 and itemid <= 12797) then - return true - end -end - -local function isWalkable(item) - if item.uid > 0 and item.uid <= 65535 then - return false - end - return true -end - -local function isPositionSafe(position) - local tile = Tile(position) - for _, item in ipairs(tile:getItems()) do - if isQuestChest(item) and not isWalkable(item) then - return false - end - end - return true -end - function onStepIn(creature, item, position, fromPosition) - if not isWalkable(item) then - if creature:isPlayer() then - local safePosition = creature:getTown():getTemplePosition() - - if position.x == fromPosition.x and position.y == fromPosition.y and position.z == fromPosition.z then - creature:teleportTo(safePosition, false) - return true - elseif not isPositionSafe(fromPosition) then - creature:teleportTo(safePosition, false) - return true - end - end - + if item.uid > 0 and item.uid <= 65535 then creature:teleportTo(fromPosition, false) end return true diff --git a/data/npc/Obi.xml b/data/npc/Obi.xml index cba5bb4..e86acaf 100644 --- a/data/npc/Obi.xml +++ b/data/npc/Obi.xml @@ -5,6 +5,73 @@ - + diff --git a/data/npc/lib/npc.lua b/data/npc/lib/npc.lua index 5fcf87b..02e86d8 100644 --- a/data/npc/lib/npc.lua +++ b/data/npc/lib/npc.lua @@ -84,7 +84,7 @@ end function doPlayerBuyItemContainer(cid, containerid, itemid, count, cost, charges) local player = Player(cid) - if not player:removeMoney(cost) then + if not player:removeTotalMoney(cost) then return false end @@ -103,5 +103,39 @@ end function getCount(string) local b, e = string:find("%d+") - return b and e and tonumber(string:sub(b, e)) or -1 + local tonumber = tonumber(string:sub(b, e)) + if tonumber > 2 ^ 32 - 1 then + print("Warning: Casting value to 32bit to prevent crash\n"..debug.traceback()) + end + return b and e and math.min(2 ^ 32 - 1, tonumber) or -1 +end + +function Player.getTotalMoney(self) + return self:getMoney() + self:getBankBalance() +end + +function isValidMoney(money) + return isNumber(money) and money > 0 +end + +function getMoneyCount(string) + local b, e = string:find("%d+") + local tonumber = tonumber(string:sub(b, e)) + if tonumber > 2 ^ 32 - 1 then + print("Warning: Casting value to 32bit to prevent crash\n"..debug.traceback()) + end + local money = b and e and math.min(2 ^ 32 - 1, tonumber) or -1 + if isValidMoney(money) then + return money + end + return -1 +end + +function getMoneyWeight(money) + local gold = money + local crystal = math.floor(gold / 10000) + gold = gold - crystal * 10000 + local platinum = math.floor(gold / 100) + gold = gold - platinum * 100 + return (ItemType(ITEM_CRYSTAL_COIN):getWeight() * crystal) + (ItemType(ITEM_PLATINUM_COIN):getWeight() * platinum) + (ItemType(ITEM_GOLD_COIN):getWeight() * gold) end diff --git a/data/npc/lib/npcsystem/keywordhandler.lua b/data/npc/lib/npcsystem/keywordhandler.lua index de84f49..e290234 100644 --- a/data/npc/lib/npcsystem/keywordhandler.lua +++ b/data/npc/lib/npcsystem/keywordhandler.lua @@ -64,6 +64,21 @@ if not KeywordHandler then return self:addChildKeywordNode(new) end + -- Adds an alias keyword. Should be used if you have to answer the same thing to several keywords. + function KeywordNode:addAliasKeyword(keywords) + if #self.children == 0 then + print('KeywordNode:addAliasKeyword no previous node found') + return false + end + + local prevNode = self.children[#self.children] + local new = KeywordNode:new(keywords, prevNode.callback, prevNode.parameters, prevNode.condition, prevNode.action) + for i = 1, #prevNode.children do + new:addChildKeywordNode(prevNode.children[i]) + end + return self:addChildKeywordNode(new) + end + -- Adds a pre-created childNode to this node. Should be used for example if several nodes should have a common child. function KeywordNode:addChildKeywordNode(childNode) self.children[#self.children + 1] = childNode @@ -157,6 +172,11 @@ if not KeywordHandler then return self:getRoot():addChildKeyword(keys, callback, parameters) end + -- Adds an alias keyword for the previous node. + function KeywordHandler:addAliasKeyword(keys) + return self:getRoot():addAliasKeyword(keys) + end + -- Moves the current position in the keyword hierarchy steps upwards. Steps defalut value = 1. function KeywordHandler:moveUp(cid, steps) if not steps or type(steps) ~= "number" then diff --git a/data/npc/lib/npcsystem/modules.lua b/data/npc/lib/npcsystem/modules.lua index fbb86f1..68d2f78 100644 --- a/data/npc/lib/npcsystem/modules.lua +++ b/data/npc/lib/npcsystem/modules.lua @@ -76,16 +76,16 @@ if Modules == nil then local player = Player(cid) if player:isPremium() or not parameters.premium then local promotion = player:getVocation():getPromotion() - if player:getStorageValue(Storage.promotion) == 1 then + if player:getStorageValue(PlayerStorageKeys.promotion) == 1 then npcHandler:say("You are already promoted!", cid) elseif player:getLevel() < parameters.level then npcHandler:say("I am sorry, but I can only promote you once you have reached level " .. parameters.level .. ".", cid) - elseif not player:removeMoney(parameters.cost) then + elseif not player:removeTotalMoney(parameters.cost) then npcHandler:say("You do not have enough money!", cid) else npcHandler:say(parameters.text, cid) player:setVocation(promotion) - player:setStorageValue(Storage.promotion, 1) + player:setStorageValue(PlayerStorageKeys.promotion, 1) end else npcHandler:say("You need a premium account in order to get promoted.", cid) @@ -110,7 +110,7 @@ if Modules == nil then npcHandler:say("You already know this spell.", cid) elseif not player:canLearnSpell(parameters.spellName) then npcHandler:say("You cannot learn this spell.", cid) - elseif not player:removeMoney(parameters.price) then + elseif not player:removeTotalMoney(parameters.price) then npcHandler:say("You do not have enough money, this spell costs " .. parameters.price .. " gold.", cid) else npcHandler:say("You have learned " .. parameters.spellName .. ".", cid) @@ -129,7 +129,7 @@ if Modules == nil then error("StdModule.bless called without any npcHandler instance.") end - if not npcHandler:isFocused(cid) or getWorldType() == WORLD_TYPE_PVP_ENFORCED then + if not npcHandler:isFocused(cid) or Game.getWorldType() == WORLD_TYPE_PVP_ENFORCED then return false end @@ -137,7 +137,7 @@ if Modules == nil then if player:isPremium() or not parameters.premium then if player:hasBlessing(parameters.bless) then npcHandler:say("Gods have already blessed you with this blessing!", cid) - elseif not player:removeMoney(parameters.cost) then + elseif not player:removeTotalMoney(parameters.cost) then npcHandler:say("You don't have enough money for blessing.", cid) else player:addBlessing(parameters.bless) @@ -166,7 +166,7 @@ if Modules == nil then npcHandler:say("First get rid of those blood stains! You are not going to ruin my vehicle!", cid) elseif parameters.level and player:getLevel() < parameters.level then npcHandler:say("You must reach level " .. parameters.level .. " before I can let you go there.", cid) - elseif not player:removeMoney(parameters.cost) then + elseif not player:removeTotalMoney(parameters.cost) then npcHandler:say("You don't have enough money.", cid) else npcHandler:say(parameters.msg or "Set the sails!", cid) @@ -365,7 +365,7 @@ if Modules == nil then elseif i == 6 then premium = temp == "true" else - print("[Warning : " .. getCreatureName(getNpcCid()) .. "] NpcSystem:", "Unknown parameter found in travel destination parameter.", temp, destination) + print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "Unknown parameter found in travel destination parameter.", temp, destination) end i = i + 1 end @@ -442,9 +442,9 @@ if Modules == nil then local player = Player(cid) if player:isPremium() or not shop_premium[cid] then - if not player:removeMoney(cost) then + if not player:removeTotalMoney(cost) then npcHandler:say("You do not have enough money!", cid) - elseif player:isPzLocked(cid) then + elseif player:isPzLocked() then npcHandler:say("Get out of there with this blood.", cid) else npcHandler:say("It was a pleasure doing business with you.", cid) @@ -471,7 +471,7 @@ if Modules == nil then return false end local parentParameters = node:getParent():getParameters() - local parseInfo = { [TAG_PLAYERNAME] = Player(cid):getName() } + local parseInfo = {[TAG_PLAYERNAME] = Player(cid):getName()} local msg = module.npcHandler:parseMessage(module.npcHandler:getMessage(MESSAGE_DECLINE), parseInfo) module.npcHandler:say(msg, cid) module.npcHandler:resetNpc(cid) @@ -489,7 +489,7 @@ if Modules == nil then local player = Player(cid) if player:isPremium() or not parameters.premium then - if player:removeMoney(cost) then + if player:removeTotalMoney(cost) then local position = player:getPosition() player:teleportTo(destination) @@ -566,6 +566,7 @@ if Modules == nil then -- Parse a string contaning a set of buyable items. function ShopModule:parseBuyable(data) + local alreadyParsedIds = {} for item in string.gmatch(data, "[^;]+") do local i = 1 @@ -593,6 +594,21 @@ if Modules == nil then end local it = ItemType(itemid) + if it:getId() == 0 then + -- invalid item + print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "Item id missing (or invalid) for parameter item:", item) + else + if alreadyParsedIds[itemid] then + if table.contains(alreadyParsedIds[itemid], subType or -1) then + print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "Found duplicated item:", item) + else + table.insert(alreadyParsedIds[itemid], subType or -1) + end + else + alreadyParsedIds[itemid] = {subType or -1} + end + end + if subType == nil and it:getCharges() ~= 0 then subType = it:getCharges() end @@ -625,6 +641,7 @@ if Modules == nil then -- Parse a string contaning a set of sellable items. function ShopModule:parseSellable(data) + local alreadyParsedIds = {} for item in string.gmatch(data, "[^;]+") do local i = 1 @@ -651,6 +668,22 @@ if Modules == nil then i = i + 1 end + local it = ItemType(itemid) + if it:getId() == 0 then + -- invalid item + print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "Item id missing (or invalid) for parameter item:", item) + else + if alreadyParsedIds[itemid] then + if table.contains(alreadyParsedIds[itemid], subType or -1) then + print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "Found duplicated item:", item) + else + table.insert(alreadyParsedIds[itemid], subType or -1) + end + else + alreadyParsedIds[itemid] = {subType or -1} + end + end + if SHOPMODULE_MODE == SHOPMODULE_MODE_TRADE then if itemid and cost then self:addSellableItem(nil, itemid, cost, realName, subType) @@ -779,12 +812,17 @@ if Modules == nil then if itemSubType == nil then itemSubType = 1 end - - local shopItem = self:getShopItem(itemid, itemSubType) - if shopItem == nil then - self.npcHandler.shopItems[#self.npcHandler.shopItems + 1] = {id = itemid, buy = cost, sell = -1, subType = itemSubType, name = realName or ItemType(itemid):getName()} - else - shopItem.buy = cost + local it = ItemType(itemid) + if it:getId() ~= 0 then + local shopItem = self:getShopItem(itemid, itemSubType) + if shopItem == nil then + self.npcHandler.shopItems[#self.npcHandler.shopItems + 1] = {id = itemid, buy = cost, sell = -1, subType = itemSubType, name = realName or it:getName()} + else + if cost < shopItem.sell then + print("[Warning : " .. Npc():getName() .. "] NpcSystem: Buy price lower than sell price: (".. shopItem.name ..")") + end + shopItem.buy = cost + end end end @@ -874,12 +912,17 @@ if Modules == nil then if itemSubType == nil then itemSubType = 0 end - - local shopItem = self:getShopItem(itemid, itemSubType) - if shopItem == nil then - self.npcHandler.shopItems[#self.npcHandler.shopItems + 1] = {id = itemid, buy = -1, sell = cost, subType = itemSubType, name = realName or ItemType(itemid):getName()} - else - shopItem.sell = cost + local it = ItemType(itemid) + if it:getId() ~= 0 then + local shopItem = self:getShopItem(itemid, itemSubType) + if shopItem == nil then + self.npcHandler.shopItems[#self.npcHandler.shopItems + 1] = {id = itemid, buy = -1, sell = cost, subType = itemSubType, name = realName or it:getName()} + else + if shopItem.buy > -1 and cost > shopItem.buy then + print("[Warning : " .. Npc():getName() .. "] NpcSystem: Sell price higher than buy price: (".. shopItem.name ..")") + end + shopItem.sell = cost + end end end @@ -923,10 +966,9 @@ if Modules == nil then return false end - local backpack = 1988 local totalCost = amount * shopItem.buy if inBackpacks then - totalCost = isItemStackable(itemid) == TRUE and totalCost + 20 or totalCost + (math.max(1, math.floor(amount / getContainerCapById(backpack))) * 20) + totalCost = ItemType(itemid):isStackable() and totalCost + 20 or totalCost + (math.max(1, math.floor(amount / ItemType(ITEM_BACKPACK):getCapacity())) * 20) end local player = Player(cid) @@ -937,7 +979,7 @@ if Modules == nil then [TAG_ITEMNAME] = shopItem.name } - if player:getMoney() < totalCost then + if player:getTotalMoney() < totalCost then local msg = self.npcHandler:getMessage(MESSAGE_NEEDMONEY) msg = self.npcHandler:parseMessage(msg, parseInfo) player:sendCancelMessage(msg) @@ -945,7 +987,7 @@ if Modules == nil then end local subType = shopItem.subType or 1 - local a, b = doNpcSellItem(cid, itemid, amount, subType, ignoreCap, inBackpacks, backpack) + local a, b = doNpcSellItem(cid, itemid, amount, subType, ignoreCap, inBackpacks, ITEM_BACKPACK) if a < amount then local msgId = MESSAGE_NEEDMORESPACE if a == 0 then @@ -959,7 +1001,7 @@ if Modules == nil then self.npcHandler.talkStart[cid] = os.time() if a > 0 then - if not player:removeMoney((a * shopItem.buy) + (b * 20)) then + if not player:removeTotalMoney((a * shopItem.buy) + (b * 20)) then return false end return true @@ -969,10 +1011,10 @@ if Modules == nil then else local msg = self.npcHandler:getMessage(MESSAGE_BOUGHT) msg = self.npcHandler:parseMessage(msg, parseInfo) - if not player:removeMoney(totalCost) then + player:sendTextMessage(MESSAGE_INFO_DESCR, msg) + if not player:removeTotalMoney(totalCost) then return false end - player:sendTextMessage(MESSAGE_INFO_DESCR, msg) self.npcHandler.talkStart[cid] = os.time() return true end @@ -999,7 +1041,7 @@ if Modules == nil then [TAG_ITEMNAME] = shopItem.name } - if not isItemFluidContainer(itemid) then + if not ItemType(itemid):isFluidContainer() then subType = -1 end @@ -1036,13 +1078,13 @@ if Modules == nil then end if itemWindow[1] == nil then - local parseInfo = { [TAG_PLAYERNAME] = Player(cid):getName() } + local parseInfo = {[TAG_PLAYERNAME] = Player(cid):getName()} local msg = module.npcHandler:parseMessage(module.npcHandler:getMessage(MESSAGE_NOSHOP), parseInfo) module.npcHandler:say(msg, cid) return true end - local parseInfo = { [TAG_PLAYERNAME] = Player(cid):getName() } + local parseInfo = {[TAG_PLAYERNAME] = Player(cid):getName()} local msg = module.npcHandler:parseMessage(module.npcHandler:getMessage(MESSAGE_SENDTRADE), parseInfo) openShopWindow(cid, itemWindow, function(cid, itemid, subType, amount, ignoreCap, inBackpacks) module.npcHandler:onBuy(cid, itemid, subType, amount, ignoreCap, inBackpacks) end, @@ -1081,14 +1123,14 @@ if Modules == nil then end elseif shop_eventtype[cid] == SHOPMODULE_BUY_ITEM then local cost = shop_cost[cid] * shop_amount[cid] - if player:getMoney() < cost then + if player:getTotalMoney() < cost then local msg = module.npcHandler:getMessage(MESSAGE_MISSINGMONEY) msg = module.npcHandler:parseMessage(msg, parseInfo) module.npcHandler:say(msg, cid) return false end - local a, b = doNpcSellItem(cid, shop_itemid[cid], shop_amount[cid], shop_subtype[cid], false, false, 1988) + local a, b = doNpcSellItem(cid, shop_itemid[cid], shop_amount[cid], shop_subtype[cid], false, false, ITEM_BACKPACK) if a < shop_amount[cid] then local msgId = MESSAGE_NEEDMORESPACE if a == 0 then @@ -1099,11 +1141,11 @@ if Modules == nil then msg = module.npcHandler:parseMessage(msg, parseInfo) module.npcHandler:say(msg, cid) if a > 0 then - if not player:removeMoney(a * shop_cost[cid]) then + if not player:removeTotalMoney(a * shop_cost[cid]) then return false end if shop_itemid[cid] == ITEM_PARCEL then - doNpcSellItem(cid, ITEM_LABEL, shop_amount[cid], shop_subtype[cid], true, false, 1988) + doNpcSellItem(cid, ITEM_LABEL, shop_amount[cid], shop_subtype[cid], true, false, ITEM_BACKPACK) end return true end @@ -1112,11 +1154,11 @@ if Modules == nil then local msg = module.npcHandler:getMessage(MESSAGE_ONBUY) msg = module.npcHandler:parseMessage(msg, parseInfo) module.npcHandler:say(msg, cid) - if not player:removeMoney(cost) then + if not player:removeTotalMoney(cost) then return false end if shop_itemid[cid] == ITEM_PARCEL then - doNpcSellItem(cid, ITEM_LABEL, shop_amount[cid], shop_subtype[cid], true, false, 1988) + doNpcSellItem(cid, ITEM_LABEL, shop_amount[cid], shop_subtype[cid], true, false, ITEM_BACKPACK) end return true end @@ -1204,4 +1246,50 @@ if Modules == nil then end return true end + + VoiceModule = { + voices = nil, + voiceCount = 0, + lastVoice = 0, + timeout = nil, + chance = nil + } + + -- VoiceModule: makes the NPC say/yell random lines from a table, with delay, chance and yell optional + function VoiceModule:new(voices, timeout, chance) + local obj = {} + setmetatable(obj, self) + self.__index = self + + obj.voices = voices + for i = 1, #obj.voices do + local voice = obj.voices[i] + if voice.yell then + voice.yell = nil + voice.talktype = TALKTYPE_YELL + else + voice.talktype = TALKTYPE_SAY + end + end + + obj.voiceCount = #voices + obj.timeout = timeout or 10 + obj.chance = chance or 10 + return obj + end + + function VoiceModule:init(handler) + return true + end + + function VoiceModule:callbackOnThink() + if self.lastVoice < os.time() then + self.lastVoice = os.time() + self.timeout + if math.random(100) <= self.chance then + local voice = self.voices[math.random(self.voiceCount)] + Npc():say(voice.text, voice.talktype) + end + end + return true + end end diff --git a/data/npc/lib/npcsystem/npchandler.lua b/data/npc/lib/npcsystem/npchandler.lua index 7ca822b..12bb035 100644 --- a/data/npc/lib/npcsystem/npchandler.lua +++ b/data/npc/lib/npcsystem/npchandler.lua @@ -319,7 +319,7 @@ if NpcHandler == nil then end local callback = self:getCallback(CALLBACK_FAREWELL) - if callback == nil or callback() then + if callback == nil or callback(cid) then if self:processModuleCallback(CALLBACK_FAREWELL) then local msg = self:getMessage(MESSAGE_FAREWELL) local player = Player(cid) @@ -521,7 +521,7 @@ if NpcHandler == nil then function NpcHandler:onWalkAway(cid) if self:isFocused(cid) then local callback = self:getCallback(CALLBACK_CREATURE_DISAPPEAR) - if callback == nil or callback() then + if callback == nil or callback(cid) then if self:processModuleCallback(CALLBACK_CREATURE_DISAPPEAR, cid) then local msg = self:getMessage(MESSAGE_WALKAWAY) diff --git a/data/reports/.gitignore b/data/reports/.gitignore deleted file mode 100644 index eb67fe0..0000000 --- a/data/reports/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Ignore user generated reports -* -!.gitignore diff --git a/data/scripts/actions/others/blessing_charms.lua b/data/scripts/actions/others/blessing_charms.lua new file mode 100644 index 0000000..650283a --- /dev/null +++ b/data/scripts/actions/others/blessing_charms.lua @@ -0,0 +1,29 @@ +local items = { + [11260] = {text = "The Spiritual Shielding protects you.", id = 1, effect = CONST_ME_LOSEENERGY}, + [11259] = {text = "The Embrace of Tibia surrounds you.", id = 2, effect = CONST_ME_MAGIC_BLUE}, + [11261] = {text = "The Fire of the Suns engulfs you.", id = 3, effect = CONST_ME_MAGIC_RED}, + [11262] = {text = "The Wisdom of Solitude inspires you.", id = 4, effect = CONST_ME_MAGIC_GREEN}, + [11258] = {text = "The Spark of the Phoenix emblazes you.", id = 5, effect = CONST_ME_FIREATTACK} +} + +local blessingCharms = Action() + +function blessingCharms.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local blessItem = items[item.itemid] + if blessItem then + if player:hasBlessing(blessItem.id) then + player:say("You already possess this blessing.", TALKTYPE_MONSTER_SAY) + return true + end + player:addBlessing(blessItem.id) + player:say(blessItem.text, TALKTYPE_MONSTER_SAY) + player:getPosition():sendMagicEffect(blessItem.effect) + item:remove() + end + return true +end + +for k, v in pairs(items) do + blessingCharms:id(k) +end +blessingCharms:register() diff --git a/data/scripts/actions/others/blue_surprise_bag.lua b/data/scripts/actions/others/blue_surprise_bag.lua new file mode 100644 index 0000000..7ec7f1c --- /dev/null +++ b/data/scripts/actions/others/blue_surprise_bag.lua @@ -0,0 +1,40 @@ +local config = { + {chanceFrom = 0, chanceTo = 1046, itemId = 6569, count = 3}, -- candy + {chanceFrom = 1047, chanceTo = 1997, itemId = 6280}, -- party cake + {chanceFrom = 1998, chanceTo = 2962, itemId = 6576}, -- fireworks rocket + {chanceFrom = 2963, chanceTo = 3872, itemId = 2114}, -- piggy bank + {chanceFrom = 3873, chanceTo = 4782, itemId = 6572}, -- party trumpet + {chanceFrom = 4783, chanceTo = 5842, itemId = 6575}, -- red balloons + {chanceFrom = 5843, chanceTo = 6725, itemId = 6578}, -- party hat + {chanceFrom = 6726, chanceTo = 7649, itemId = 6394}, -- cream cake + {chanceFrom = 7650, chanceTo = 8478, itemId = 6577}, -- green balloons + {chanceFrom = 8479, chanceTo = 9212, itemId = 2687, count = 10}, -- cookie + {chanceFrom = 9213, chanceTo = 10000, itemId = 6574} -- bar of chocolate +} + +local blueSurpriseBag = Action() + +function blueSurpriseBag.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local chance = math.random(0, 10000) + for i = 1, #config do + local randomItem = config[i] + if chance >= randomItem.chanceFrom and chance <= randomItem.chanceTo then + if randomItem.itemId then + local gift = randomItem.itemId + local count = randomItem.count or 1 + if type(count) == "table" then + count = math.random(count[1], count[2]) + end + player:addItem(gift, count) + end + + item:getPosition():sendMagicEffect(CONST_ME_GIFT_WRAPS) + item:remove(1) + return true + end + end + return false +end + +blueSurpriseBag:id(6570) +blueSurpriseBag:register() diff --git a/data/scripts/actions/others/christmas_bundle.lua b/data/scripts/actions/others/christmas_bundle.lua new file mode 100644 index 0000000..d858d7c --- /dev/null +++ b/data/scripts/actions/others/christmas_bundle.lua @@ -0,0 +1,70 @@ +local christmasBundle = Action() + +function christmasBundle.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local presents = { -- [christmas bundle item id] = {{reward item id, count}, ...} + [6507] = { -- red bundle + {6569, 15}, -- candy + {2687, 20}, -- cookie + {2688, 10}, -- candy cane + {2675, 10}, -- orange + {2674, 5}, -- red apple + 6501, -- gingerbreadman + 6502, -- christmas wreath + 6490, -- christmas branch + 6504, -- red christmas garland + 6388 -- christmas card + }, + [6508] = { -- blue bundle + {6569, 15}, -- candy + {2687, 20}, -- cookie + {2688, 10}, -- candy cane + {2675, 10}, -- orange + {2674, 5}, -- red apple + 6501, -- gingerbreadman + 6502, -- christmas wreath + 6490, -- christmas branch + 6505, -- blue christmas garland + 6388 -- christmas card + }, + [6509] = { -- green bundle + {6569, 15}, -- candy + {2687, 20}, -- cookie + {2688, 10}, -- candy cane + {2675, 10}, -- orange + {2674, 5}, -- red apple + 6501, -- gingerbreadman + 6502, -- christmas wreath + 6490, -- christmas branch + 6503, -- christmas garland + 6388 -- christmas card + } + } + + local targetItem = presents[item.itemid] + if not targetItem then + return true + end + + local rewards = {} + while #rewards < 7 do + local count = 1 + local rand = math.random(#targetItem) + local gift = targetItem[rand] + if type(gift) == "table" then + gift, count = unpack(gift) + end + rewards[#rewards + 1] = {gift, count} + table.remove(targetItem, rand) + end + + for i = 1, #rewards do + player:addItem(unpack(rewards[i])) + end + item:remove(1) + player:getPosition():sendMagicEffect(CONST_ME_GIFT_WRAPS) + player:addAchievementProgress("Santa's Li'l Helper", 25) + return true +end + +christmasBundle:id(6507,6508,6509) +christmasBundle:register() diff --git a/data/scripts/actions/others/clay_lump.lua b/data/scripts/actions/others/clay_lump.lua new file mode 100644 index 0000000..29110f3 --- /dev/null +++ b/data/scripts/actions/others/clay_lump.lua @@ -0,0 +1,43 @@ +local config = { + {chance = {0.0, 1.54}, transformId = 11342, description = "This little figurine of Brog, the raging Titan, was skillfully made by |PLAYERNAME|.", achievement = true}, + {chance = {1.54, 9.16}, transformId = 11341, description = "It was made by |PLAYERNAME| and is clearly a little figurine of.. hm, one does not recognise that yet."}, + {chance = {9.16, 25.48}, transformId = 11340, description = "It was made by |PLAYERNAME|, whose potter skills could use some serious improvement."}, + {chance = {25.48, 100.0}, remove = true, sound = "Aw man. That did not work out too well."} +} + +local clayLump = Action() + +function clayLump.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local random, tmpItem = math.random(0, 10000) * 0.01 + for i = 1, #config do + tmpItem = config[i] + if random >= tmpItem.chance[1] and random < tmpItem.chance[2] then + item:getPosition():sendMagicEffect(CONST_ME_POFF) + + if tmpItem.remove then + item:remove() + else + item:transform(tmpItem.transformId) + end + + if tmpItem.sound then + player:say(tmpItem.sound, TALKTYPE_MONSTER_SAY, false, player) + end + + if tmpItem.description then + item:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, tmpItem.description:gsub('|PLAYERNAME|', player:getName())) + end + + if tmpItem.achievement then + player:addAchievement("Clay Fighter") + player:addAchievementProgress("Clay to Fame", 5) + end + + break + end + end + return true +end + +clayLump:id(11339) +clayLump:register() diff --git a/data/scripts/actions/others/costume_bag.lua b/data/scripts/actions/others/costume_bag.lua new file mode 100644 index 0000000..ce32649 --- /dev/null +++ b/data/scripts/actions/others/costume_bag.lua @@ -0,0 +1,24 @@ +local config = { + [9075] = {"orc warrior", "pirate cutthroat", "dworc voodoomaster", "dwarf guard", "minotaur mage"}, -- common + [9077] = {"serpent spawn", "demon", "juggernaut", "behemoth", "ashmunrah"}, -- deluxe + [9076] = {"quara hydromancer", "diabolic imp", "banshee", "frost giant", "lich"} -- uncommon +} + +local costumeBag = Action() + +function costumeBag.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local creatures = config[item.itemid] + if not creatures then + return true + end + player:setMonsterOutfit(creatures[math.random(#creatures)], 5 * 60 * 10 * 1000) + player:addAchievementProgress("Masquerader", 100) + item:getPosition():sendMagicEffect(CONST_ME_MAGIC_RED) + item:remove() + return true +end + +for k,v in pairs(config) do + costumeBag:id(k) +end +costumeBag:register() diff --git a/data/scripts/actions/others/crate_usable.lua b/data/scripts/actions/others/crate_usable.lua new file mode 100644 index 0000000..d4abbd1 --- /dev/null +++ b/data/scripts/actions/others/crate_usable.lua @@ -0,0 +1,43 @@ +local rewards = { -- chanceMin, chanceMax, itemID, count + {1, 36}, -- nothing + {37, 46, 2148, 80}, -- gold coin + {47, 55, 2148, 50}, -- gold coin + {56, 64, 2671, 5}, -- ham + {65, 73, 2789, 5}, -- brown mushroom + {74, 81, 7620}, -- mana potion + {82, 87, 7618}, -- health potion + {88, 92, 9811}, -- rusty legs (common) + {93, 96, 9808}, -- rusty armor (common) + {97, 100, 2213} -- dwarven ring +} + +local crateUsable = Action() + +function crateUsable.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if (player:getStorageValue(PlayerStorageKeys.crateUsable)) <= os.time() then + local totalChance = math.random(100) + for i = 1, #rewards do + local reward = rewards[i] + if totalChance >= reward[1] and totalChance <= reward[2] then + if reward[3] then + local item = ItemType(reward[3]) + local count = reward[4] or 1 + player:addItem(reward[3], count) + local str = ("You found %s %s!"):format(count > 1 and count or item:getArticle(), count > 1 and item:getPluralName() or item:getName()) + player:say(str, TALKTYPE_MONSTER_SAY, false, player, toPosition) + player:setStorageValue(PlayerStorageKeys.crateUsable, os.time() + 1 * 60 * 60) + player:addAchievementProgress("Free Items!", 50) + else + player:say("You found nothing useful.", TALKTYPE_MONSTER_SAY, false, player, toPosition) + end + break + end + end + else + player:say("You found nothing useful.", TALKTYPE_MONSTER_SAY, false, player, toPosition) + end + return true +end + +crateUsable:id(9661) +crateUsable:register() diff --git a/data/scripts/actions/others/dolls.lua b/data/scripts/actions/others/dolls.lua new file mode 100644 index 0000000..606ff53 --- /dev/null +++ b/data/scripts/actions/others/dolls.lua @@ -0,0 +1,100 @@ +local dollsTable = { + [5080] = {"Hug me!"}, + [5669] = { + "It's not winning that matters, but winning in style.", + "Today's your lucky day. Probably.", + "Do not meddle in the affairs of dragons, for you are crunchy and taste good with ketchup.", + "That is one stupid question.", + "You'll need more rum for that.", + "Do or do not. There is no try.", + "You should do something you always wanted to.", + "If you walk under a ladder and it falls down on you it probably means bad luck.", + "Never say 'oops'. Always say 'Ah, interesting!'", + "Five steps east, fourteen steps south, two steps north and seventeen steps west!" + }, + [6566] = { + "Fchhhhhh!", + "Zchhhhhh!", + "Grooaaaaar*cough*", + "Aaa... CHOO!", + "You... will.... burn!!" + }, + [6388] = {"Merry Christmas |PLAYERNAME|."}, + [6512] = { + "Ho ho ho", + "Jingle bells, jingle bells...", + "Have you been naughty?", + "Have you been nice?", + "Merry Christmas!", + "Can you stop squeezing me now... I'm starting to feel a little sick." + }, + [8974] = {"ARE YOU PREPARED TO FACE YOUR DESTINY?"}, + [8977] = { + "Weirdo, you're a weirdo! Actually all of you are!", + "Pie for breakfast, pie for lunch and pie for dinner!", + "All hail the control panel!", + "I own, god owns, perfect match!", + "Hug me! Feed me! Hail me!" + }, + [8981] = { + "It's news to me.", + "News, updated as infrequently as possible!", + "Extra! Extra! Read all about it!", + "Fresh off the press!" + }, + [8982] = { + "Hail!", + "So cold.", + "Run, mammoth!" + }, + [10063] = { + "Hail |PLAYERNAME|! Hail!", + "Hauopa!", + "WHERE IS MY HYDROMEL?!", + "Yala Boom" + } +} + +local dolls = Action() + +function dolls.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local sounds = dollsTable[item.itemid] + if not sounds then + return false + end + + if fromPosition.x == CONTAINER_POSITION then + fromPosition = player:getPosition() + end + + local chance = math.random(#sounds) + local sound = sounds[chance] + if item.itemid == 6566 then + if chance == 3 then + fromPosition:sendMagicEffect(CONST_ME_POFF) + elseif chance == 4 then + fromPosition:sendMagicEffect(CONST_ME_FIREAREA) + elseif chance == 5 then + doTargetCombat(0, player, COMBAT_PHYSICALDAMAGE, -1, -1, CONST_ME_EXPLOSIONHIT) + end + elseif item.itemid == 5669 then + player:addAchievementProgress("Superstitious", 100) + fromPosition:sendMagicEffect(CONST_ME_MAGIC_RED) + item:transform(item.itemid + 1) + item:decay() + elseif item.itemid == 6388 then + fromPosition:sendMagicEffect(CONST_ME_SOUND_YELLOW) + elseif item.itemid == 10063 then + item:transform(item.itemid + 1) + item:decay() + end + + sound = sound:gsub('|PLAYERNAME|', player:getName()) + player:say(sound, TALKTYPE_MONSTER_SAY, false, 0, fromPosition) + return true +end + +for k, v in pairs(dollsTable) do + dolls:id(k) +end +dolls:register() diff --git a/data/scripts/actions/others/doors.lua b/data/scripts/actions/others/doors.lua new file mode 100644 index 0000000..b9cfeca --- /dev/null +++ b/data/scripts/actions/others/doors.lua @@ -0,0 +1,138 @@ +local positionOffsets = { + Position(1, 0, 0), -- east + Position(0, 1, 0), -- south + Position(-1, 0, 0), -- west + Position(0, -1, 0) -- north +} + +--[[ +When closing a door with a creature in it findPushPosition will find the most appropriate +adjacent position following a prioritization order. +The function returns the position of the first tile that fulfills all the checks in a round. +The function loops trough east -> south -> west -> north on each following line in that order. +In round 1 it checks if there's an unhindered walkable tile without any creature. +In round 2 it checks if there's a tile with a creature. +In round 3 it checks if there's a tile blocked by a movable tile-blocking item. +In round 4 it checks if there's a tile blocked by a magic wall or wild growth. +]] +local function findPushPosition(creature, round) + local pos = creature:getPosition() + for _, offset in ipairs(positionOffsets) do + local offsetPosition = pos + offset + local tile = Tile(offsetPosition) + if tile then + local creatureCount = tile:getCreatureCount() + if round == 1 then + if tile:queryAdd(creature) == RETURNVALUE_NOERROR and creatureCount == 0 then + if not tile:hasFlag(TILESTATE_PROTECTIONZONE) or (tile:hasFlag(TILESTATE_PROTECTIONZONE) and creature:canAccessPz()) then + return offsetPosition + end + end + elseif round == 2 then + if creatureCount > 0 then + if not tile:hasFlag(TILESTATE_PROTECTIONZONE) or (tile:hasFlag(TILESTATE_PROTECTIONZONE) and creature:canAccessPz()) then + return offsetPosition + end + end + elseif round == 3 then + local topItem = tile:getTopDownItem() + if topItem then + if topItem:getType():isMovable() then + return offsetPosition + end + end + else + if tile:getItemById(ITEM_MAGICWALL) or tile:getItemById(ITEM_WILDGROWTH) then + return offsetPosition + end + end + end + end + if round < 4 then + return findPushPosition(creature, round + 1) + end +end + +local door = Action() + +function door.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local itemId = item:getId() + if table.contains(closedQuestDoors, itemId) then + if player:getStorageValue(item.actionid) ~= -1 then + item:transform(itemId + 1) + player:teleportTo(toPosition, true) + else + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The door seems to be sealed against unwanted intruders.") + end + return true + elseif table.contains(closedLevelDoors, itemId) then + if item.actionid > 0 and player:getLevel() >= item.actionid - actionIds.levelDoor then + item:transform(itemId + 1) + player:teleportTo(toPosition, true) + else + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Only the worthy may pass.") + end + return true + elseif table.contains(keys, itemId) then + local tile = Tile(toPosition) + if not tile then + return false + end + target = tile:getTopVisibleThing() + if target.actionid == 0 then + return false + end + if table.contains(keys, target.itemid) then + return false + end + if not table.contains(openDoors, target.itemid) and not table.contains(closedDoors, target.itemid) and not table.contains(lockedDoors, target.itemid) then + return false + end + if item.actionid ~= target.actionid then + player:sendTextMessage(MESSAGE_STATUS_SMALL, "The key does not match.") + return true + end + local transformTo = target.itemid + 2 + if table.contains(openDoors, target.itemid) then + transformTo = target.itemid - 2 + elseif table.contains(closedDoors, target.itemid) then + transformTo = target.itemid - 1 + end + target:transform(transformTo) + return true + elseif table.contains(lockedDoors, itemId) then + player:sendTextMessage(MESSAGE_INFO_DESCR, "It is locked.") + return true + elseif table.contains(openDoors, itemId) or table.contains(openExtraDoors, itemId) or table.contains(openHouseDoors, itemId) then + local creaturePositionTable = {} + local doorCreatures = Tile(toPosition):getCreatures() + if doorCreatures and #doorCreatures > 0 then + for _, doorCreature in pairs(doorCreatures) do + local pushPosition = findPushPosition(doorCreature, 1) + if not pushPosition then + player:sendCancelMessage(RETURNVALUE_NOTENOUGHROOM) + return true + end + table.insert(creaturePositionTable, {creature = doorCreature, position = pushPosition}) + end + for _, tableCreature in ipairs(creaturePositionTable) do + tableCreature.creature:teleportTo(tableCreature.position, true) + end + end + + item:transform(itemId - 1) + return true + elseif table.contains(closedDoors, itemId) or table.contains(closedExtraDoors, itemId) or table.contains(closedHouseDoors, itemId) then + item:transform(itemId + 1) + return true + end + return false +end + +local doorTables = {keys, openDoors, closedDoors, lockedDoors, openExtraDoors, closedExtraDoors, openHouseDoors, closedHouseDoors, closedQuestDoors, closedLevelDoors} +for _, doors in pairs(doorTables) do + for _, doorId in pairs(doors) do + door:id(doorId) + end +end +door:register() diff --git a/data/scripts/actions/others/explosive_present.lua b/data/scripts/actions/others/explosive_present.lua new file mode 100644 index 0000000..c79eac4 --- /dev/null +++ b/data/scripts/actions/others/explosive_present.lua @@ -0,0 +1,11 @@ +local explosivePresent = Action() + +function explosivePresent.onUse(player, item, fromPosition, target, toPosition, isHotkey) + player:say("KABOOOOOOOOOOM!", TALKTYPE_MONSTER_SAY) + player:getPosition():sendMagicEffect(CONST_ME_FIREAREA) + item:remove() + return true +end + +explosivePresent:id(9074) +explosivePresent:register() diff --git a/data/scripts/actions/others/flower_pot.lua b/data/scripts/actions/others/flower_pot.lua new file mode 100644 index 0000000..9dbe80c --- /dev/null +++ b/data/scripts/actions/others/flower_pot.lua @@ -0,0 +1,73 @@ +local flowers = { + {itemid = 7655, watered = false, advance = false, msg = "You should plant some seeds first."}, + {itemid = 7665, watered = true, advance = false, msg = "You watered your plant.", after = 7673}, + {itemid = 7673, watered = false, advance = false, msg = "Your plant doesn't need water."}, + {itemid = 7670, watered = false, advance = false, msg = "Your plant doesn't need water."}, + {itemid = 7680, watered = false, advance = false, msg = "Your plant doesn't need water."}, + {itemid = 7682, watered = false, advance = false, msg = "Your plant doesn't need water."}, + {itemid = 7684, watered = false, advance = false, msg = "Your plant doesn't need water."}, + {itemid = 7686, watered = false, advance = false, msg = "Your plant doesn't need water."}, + {itemid = 7688, watered = false, advance = false, msg = "Your plant doesn't need water."}, + {itemid = 7690, watered = false, advance = false, msg = "Your plant doesn't need water."}, + {itemid = 7992, watered = false, advance = false, msg = "Your plant doesn't need water."}, + {itemid = 7994, watered = false, advance = false, msg = "Your plant doesn't need water."}, + {itemid = 9982, watered = false, advance = false, msg = "Your plant doesn't need water."}, + {itemid = 9990, watered = false, advance = false, msg = "Your plant doesn't need water."}, + {itemid = 7692, watered = false, advance = false, msg = "Your plant doesn't need water."}, + {itemid = 7694, watered = false, advance = false, msg = "Your plant doesn't need water."}, + {itemid = 9986, watered = false, advance = false, msg = "Your plant doesn't need water."}, + {itemid = 9988, watered = false, advance = false, msg = "Your plant doesn't need water."}, + {itemid = 7689, watered = true, advance = false, msg = "You watered your plant.", after = 7688}, + {itemid = 7691, watered = true, advance = false, msg = "You watered your plant.", after = 7690}, + {itemid = 7693, watered = true, advance = false, msg = "You watered your plant.", after = 7692}, + {itemid = 7695, watered = true, advance = false, msg = "You watered your plant.", after = 7694}, + {itemid = 9991, watered = true, advance = false, msg = "You watered your plant.", after = 9990}, + {itemid = 9989, watered = true, advance = false, msg = "You watered your plant.", after = 9988}, + {itemid = 7674, watered = true, advance = false, msg = "You finally remembered to water your plant and it recovered.", after = 7688}, + {itemid = 7675, watered = true, advance = false, msg = "You finally remembered to water your plant and it recovered.", after = 7690}, + {itemid = 7676, watered = true, advance = false, msg = "You finally remembered to water your plant and it recovered.", after = 7692}, + {itemid = 7677, watered = true, advance = false, msg = "You finally remembered to water your plant and it recovered.", after = 7694}, + {itemid = 9984, watered = true, advance = false, msg = "You finally remembered to water your plant and it recovered.", after = 9990}, + {itemid = 9985, watered = true, advance = false, msg = "You finally remembered to water your plant and it recovered.", after = 9988}, + {itemid = 7679, watered = true, advance = true, msg = {"You watered your plant.","Your plant has grown to the next stage!"}, after = {7673, 7670}, chance = 80}, + {itemid = 7681, watered = true, advance = true, msg = {"You watered your plant.","Your plant has grown to the next stage!"}, after = {7680, 7688}, chance = 80}, + {itemid = 7683, watered = true, advance = true, msg = {"You watered your plant.","Your plant has grown to the next stage!"}, after = {7682, 7690}, chance = 80}, + {itemid = 7685, watered = true, advance = true, msg = {"You watered your plant.","Your plant has grown to the next stage!"}, after = {7684, 7692}, chance = 80}, + {itemid = 7687, watered = true, advance = true, msg = {"You watered your plant.","Your plant has grown to the next stage!"}, after = {7686, 7694}, chance = 80}, + {itemid = 9983, watered = true, advance = true, msg = {"You watered your plant.","Your plant has grown to the next stage!"}, after = {9982, 9990}, chance = 80}, + {itemid = 9987, watered = true, advance = true, msg = {"You watered your plant.","Your plant has grown to the next stage!"}, after = {9986, 9988}, chance = 80}, + {itemid = 7678, watered = true, advance = true, msg = {"You watered your plant.","Your plant has grown to the next stage!"}, after = {7670, 7680, 7682, 7684, 7686, 9982, 9986}, chance = 80}, +} + +local flowerPot = Action() + +function flowerPot.onUse(player, item, fromPosition, target, toPosition, isHotkey) + for _, flower in pairs(flowers) do + if target.itemid == flower.itemid then + if (flower.watered == false and flower.advance == false) then + player:say(flower.msg, TALKTYPE_MONSTER_SAY) + elseif (flower.watered == true and flower.advance == false) then + target:transform(flower.after) + player:say(flower.msg, TALKTYPE_MONSTER_SAY) + toPosition:sendMagicEffect(CONST_ME_LOSEENERGY) + target:decay() + elseif (flower.watered == true and flower.advance == true) then + local i = 1 + if (math.random(100) <= flower.chance) then + i = 2 + target:transform(flower.after[math.random(2, #flower.after)]) + else + target:transform(flower.after[i]) + end + toPosition:sendMagicEffect(CONST_ME_LOSEENERGY) + player:say(flower.msg[i], TALKTYPE_MONSTER_SAY) + target:decay() + end + break + end + end + return true +end + +flowerPot:id(7734) -- watering can +flowerPot:register() diff --git a/data/scripts/actions/others/goldfish_bowl.lua b/data/scripts/actions/others/goldfish_bowl.lua new file mode 100644 index 0000000..dbe8955 --- /dev/null +++ b/data/scripts/actions/others/goldfish_bowl.lua @@ -0,0 +1,15 @@ +local goldfishBowl = Action() + +function goldfishBowl.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if target.itemid ~= 5554 then + return false + end + + target:remove(1) + item:transform(5929) + player:addAchievement("Silent Pet") + return true +end + +goldfishBowl:id(5928) +goldfishBowl:register() diff --git a/data/scripts/actions/others/lottery_ticket.lua b/data/scripts/actions/others/lottery_ticket.lua new file mode 100644 index 0000000..0c7ebba --- /dev/null +++ b/data/scripts/actions/others/lottery_ticket.lua @@ -0,0 +1,16 @@ +local lotteryTicket = Action() + +function lotteryTicket.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if math.random(98) == 1 then + player:getPosition():sendMagicEffect(CONST_ME_SOUND_YELLOW) + item:transform(5958) + else + player:addAchievementProgress("Jinx", 500) + item:getPosition():sendMagicEffect(CONST_ME_POFF) + item:remove(1) + end + return true +end + +lotteryTicket:id(5957) +lotteryTicket:register() diff --git a/data/scripts/actions/others/red_surprise_bag.lua b/data/scripts/actions/others/red_surprise_bag.lua new file mode 100644 index 0000000..e378715 --- /dev/null +++ b/data/scripts/actions/others/red_surprise_bag.lua @@ -0,0 +1,45 @@ +local config = { + {chanceFrom = 0, chanceTo = 4760, itemId = 6576}, -- fireworks rocket + {chanceFrom = 4761, chanceTo = 6841, itemId = 6394}, -- cream cake + {chanceFrom = 6842, chanceTo = 7975, itemId = 6574}, -- bar of chocolate + {chanceFrom = 7976, chanceTo = 9007, itemId = 6578}, -- party hat + {chanceFrom = 9008, chanceTo = 9519, itemId = 2114}, -- piggy bank + {chanceFrom = 9520, chanceTo = 9692, itemId = 2153}, -- violet gem + {chanceFrom = 9693, chanceTo = 9850, itemId = 5944}, -- soul orb + {chanceFrom = 9851, chanceTo = 9885, itemId = 2156}, -- red gem + {chanceFrom = 9886, chanceTo = 9907, itemId = 2112}, -- teddy bear + {chanceFrom = 9908, chanceTo = 9925, itemId = 2520}, -- demon shield + {chanceFrom = 9926, chanceTo = 9944, itemId = 6568}, -- panda teddy + {chanceFrom = 9945, chanceTo = 9959, itemId = 2195}, -- boots of haste + {chanceFrom = 9960, chanceTo = 9974, itemId = 2492}, -- dragon scale mail + {chanceFrom = 9975, chanceTo = 9986, itemId = 2498}, -- royal helmet + {chanceFrom = 9987, chanceTo = 9995, itemId = 2173}, -- amulet of loss + {chanceFrom = 9996, chanceTo = 10000, itemId = 6566}, -- stuffed dragon +} + +local redSurpriseBag = Action() + +function redSurpriseBag.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local chance = math.random(0, 10000) + for i = 1, #config do + local randomItem = config[i] + if chance >= randomItem.chanceFrom and chance <= randomItem.chanceTo then + if randomItem.itemId then + local gift = randomItem.itemId + local count = randomItem.count or 1 + if type(count) == "table" then + count = math.random(count[1], count[2]) + end + player:addItem(gift, count) + end + + item:getPosition():sendMagicEffect(CONST_ME_GIFT_WRAPS) + item:remove(1) + return true + end + end + return false +end + +redSurpriseBag:id(6571) +redSurpriseBag:register() diff --git a/data/scripts/actions/others/spellbook.lua b/data/scripts/actions/others/spellbook.lua new file mode 100644 index 0000000..0422d84 --- /dev/null +++ b/data/scripts/actions/others/spellbook.lua @@ -0,0 +1,36 @@ +local spellbook = Action() + +function spellbook.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local text = {} + local spells = {} + for _, spell in ipairs(player:getInstantSpells()) do + if spell.level ~= 0 then + if spell.manapercent > 0 then + spell.mana = spell.manapercent .. "%" + end + spells[#spells + 1] = spell + end + end + + table.sort(spells, function(a, b) return a.level < b.level end) + + local prevLevel = -1 + for i, spell in ipairs(spells) do + if prevLevel ~= spell.level then + if i == 1 then + text[#text == nil and 1 or #text+1] = "Spells for Level " + else + text[#text+1] = "\nSpells for Level " + end + text[#text+1] = spell.level .. "\n" + prevLevel = spell.level + end + text[#text+1] = spell.words .. " - " .. spell.name .. " : " .. spell.mana .. "\n" + end + + player:showTextDialog(item:getId(), table.concat(text)) + return true +end + +spellbook:id(2175, 6120, 8900, 8901, 8902, 8903, 8904, 8918) +spellbook:register() diff --git a/data/scripts/actions/others/spellwand.lua b/data/scripts/actions/others/spellwand.lua new file mode 100644 index 0000000..f74062b --- /dev/null +++ b/data/scripts/actions/others/spellwand.lua @@ -0,0 +1,30 @@ +local monsters = {"Rat", "Green Frog", "Chicken"} + +local spellwand = Action() + +function spellwand.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if player:getTile():hasFlag(TILESTATE_PROTECTIONZONE) then + player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return true + end + if not target:isPlayer() then + player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return true + end + if math.random(100) <= 33 then + item:remove() + player:say("The spellwand broke.", TALKTYPE_MONSTER_SAY) + if math.random(100) <= 75 and player:getStorageValue(PlayerStorageKeys.madSheepSummon) <= os.time() then + Game.createMonster("Mad Sheep", fromPosition) + player:setStorageValue(PlayerStorageKeys.madSheepSummon, os.time() + 12 * 60 * 60) + end + else + target:setMonsterOutfit(monsters[math.random(#monsters)], 60 * 1000) + target:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) + return true + end + return true +end + +spellwand:id(7735) +spellwand:register() diff --git a/data/scripts/actions/others/spider_egg.lua b/data/scripts/actions/others/spider_egg.lua new file mode 100644 index 0000000..b340ea3 --- /dev/null +++ b/data/scripts/actions/others/spider_egg.lua @@ -0,0 +1,20 @@ +local spiderEgg = Action() + +function spiderEgg.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local chance = math.random(100) + if chance >= 50 and chance < 83 then + Game.createMonster("Spider", fromPosition) + elseif chance >= 83 and chance < 97 then + Game.createMonster("Poison Spider", fromPosition) + elseif chance >= 97 and chance < 100 then + Game.createMonster("Tarantula", fromPosition) + else + item:getPosition():sendMagicEffect(CONST_ME_POFF) + end + item:transform(7536) + item:decay() + return true +end + +spiderEgg:id(7537) +spiderEgg:register() diff --git a/data/scripts/actions/others/suspicious_surprise_bag.lua b/data/scripts/actions/others/suspicious_surprise_bag.lua new file mode 100644 index 0000000..261e8ae --- /dev/null +++ b/data/scripts/actions/others/suspicious_surprise_bag.lua @@ -0,0 +1,40 @@ +local config = { + {chanceFrom = 0, chanceTo = 3394}, -- nothing + {chanceFrom = 3395, chanceTo = 5159, itemId = 1689}, -- yellow pillow + {chanceFrom = 5160, chanceTo = 6954, itemId = 7735}, -- spellwand + {chanceFrom = 6955, chanceTo = 8327, itemId = 2114}, -- piggy bank + {chanceFrom = 8328, chanceTo = 9141, itemId = 6574}, -- bar of chocolate + {chanceFrom = 9142, chanceTo = 9654, itemId = 6394}, -- cream cake + {chanceFrom = 9655, chanceTo = 9850, itemId = 7377}, -- ice cream cone + {chanceFrom = 9851, chanceTo = 9986, itemId = 9074}, -- explosive present + {chanceFrom = 9987, chanceTo = 10000, itemId = 7487} -- toy mouse +} + +local suspiciousSurpriseBag = Action() + +function suspiciousSurpriseBag.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local chance = math.random(0, 10000) + for i = 1, #config do + local randomItem = config[i] + if chance >= randomItem.chanceFrom and chance <= randomItem.chanceTo then + if randomItem.itemId then + local gift = randomItem.itemId + local count = randomItem.count or 1 + if type(count) == "table" then + count = math.random(count[1], count[2]) + end + player:addItem(gift, count) + item:getPosition():sendMagicEffect(CONST_ME_GIFT_WRAPS) + else + item:getPosition():sendMagicEffect(CONST_ME_CAKE) + end + + item:remove(1) + return true + end + end + return false +end + +suspiciousSurpriseBag:id(9108) +suspiciousSurpriseBag:register() diff --git a/data/scripts/actions/others/transforms.lua b/data/scripts/actions/others/transforms.lua new file mode 100644 index 0000000..023de90 --- /dev/null +++ b/data/scripts/actions/others/transforms.lua @@ -0,0 +1,87 @@ +local transformItems = { + [1479] = 1480, [1480] = 1479, -- street lamp + [1634] = 1635, [1635] = 1634, -- table + [1636] = 1637, [1637] = 1636, -- table + [1638] = 1639, [1639] = 1638, -- table + [1640] = 1641, [1641] = 1640, -- table + [1786] = 1787, [1787] = 1786, -- oven + [1788] = 1789, [1789] = 1788, -- oven + [1790] = 1791, [1791] = 1790, -- oven + [1792] = 1793, [1793] = 1792, -- oven + [1945] = 1946, [1946] = 1945, -- lever + [2037] = 2038, [2038] = 2037, -- wall lamp + [2039] = 2040, [2040] = 2039, -- wall lamp + [2058] = 2059, [2059] = 2058, -- torch bearer + [2060] = 2061, [2061] = 2060, -- torch bearer + [2064] = 2065, [2065] = 2064, -- table lamp + [2066] = 2067, [2067] = 2066, -- wall lamp + [2068] = 2069, [2069] = 2068, -- wall lamp + [2096] = 2097, [2097] = 2096, -- pumpkinhead + [2578] = 2579, -- trap + [3697] = 3698, [3698] = 3697, -- sacred statue + [3699] = 3700, [3700] = 3699, -- sacred statue + [3743] = 4404, [4404] = 3743, -- bamboo lamp + [3943] = 3944, [3944] = 3943, -- torch bearer + [3945] = 3946, [3946] = 3945, -- torch bearer + [3947] = 3948, [3948] = 3947, -- wall lamp + [3949] = 3950, [3950] = 3949, -- wall lamp + [6489] = 6490, [6490] = 6489, -- christmas branch + [7058] = 7059, [7059] = 7058, -- skull pillar + [8684] = 8685, [8685] = 8684, -- chimney + [8686] = 8687, [8687] = 8686, -- chimney + [8688] = 8689, [8689] = 8688, -- chimney + [8690] = 8691, [8691] = 8690, -- chimney + [9575] = 9576, [9576] = 9575, -- street lamp (yalahar) + [9577] = 9578, [9578] = 9577, -- street lamp (yalahar) + [9579] = 9580, [9580] = 9579, -- street lamp (yalahar) + [9581] = 9582, [9582] = 9581, -- street lamp (yalahar) + [9747] = 9748, [9748] = 9747, -- wall lamp + [9749] = 9750, [9750] = 9749, -- wall lamp + [9825] = 9826, [9826] = 9825, -- lever + [9827] = 9828, [9828] = 9827, -- lever + [9838] = 9839, [9839] = 9838, -- wall lamp + [9840] = 9841, [9841] = 9840, -- wall lamp + [9842] = 9843, [9843] = 9842, -- wall lamp + [9844] = 9845, [9845] = 9844, -- wall lamp + [9976] = 9977, -- crystal pedestal + [9977] = 9978, -- crystal pedestal + [9978] = 9979, -- crystal pedestal + [9979] = 9976, -- crystal pedestal + [10029] = 10030, [10030] = 10029, -- lever + [10044] = 10045, [10045] = 10044, -- lever + [10971] = 10972, [10972] = 10971, -- wall lamp + [10973] = 10974, [10974] = 10973, -- wall lamp + [10999] = 11000, [11000] = 10999, -- dragon statue (lamp) + [11001] = 11002, [11002] = 11001, -- dragon statue (lamp) + [11915] = 11916, [11916] = 11915, -- dragon basin + [11995] = 11996, [11996] = 11995, -- torch + [11997] = 11998, [11998] = 11997, -- torch + [11999] = 12000, [12000] = 11999, -- dragon statue (lamp) + [12001] = 12002, [12002] = 12001, -- dragon statue (lamp) + [12006] = 12005, [12005] = 12006, -- jade basin + [12007] = 12008, [12008] = 12007, -- jade basin + [12014] = 12016, [12016] = 12014, -- mystic floor lamp + [12015] = 12017, [12017] = 12015, -- mystic floor lamp + [12191] = 12192, [12192] = 12191, -- wall torch + [12200] = 12201, [12201] = 12200, -- wall torch + [12208] = 12209, [12209] = 12208, -- wall lamp + [12210] = 12211, [12211] = 12210, -- wall lamp +} + +local transformTo = Action() + +function transformTo.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local transformIds = transformItems[item:getId()] + if not transformIds then + return false + end + + item:transform(transformIds) + return true +end + +for i, v in pairs(transformItems) do + transformTo:id(i) +end + +transformTo:register() diff --git a/data/scripts/actions/others/voodoo_doll.lua b/data/scripts/actions/others/voodoo_doll.lua new file mode 100644 index 0000000..fd190b6 --- /dev/null +++ b/data/scripts/actions/others/voodoo_doll.lua @@ -0,0 +1,22 @@ +local voodooDoll = Action() + +function voodooDoll.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if target.itemid ~= 1 or not target:isPlayer() then + return false + end + + local text = "" + if math.random(100) <= 5 then + text = "You concentrate on your victim and hit the needle in the doll." + player:addAchievement("Dark Voodoo Priest") + toPosition:sendMagicEffect(CONST_ME_DRAWBLOOD, player) + else + text = "You concentrate on your victim, hit the needle in the doll.......but nothing happens." + end + + player:say(text, TALKTYPE_MONSTER_SAY, false, player) + return true +end + +voodooDoll:id(3955) +voodooDoll:register() diff --git a/data/scripts/actions/others/windows.lua b/data/scripts/actions/others/windows.lua new file mode 100644 index 0000000..b4135c4 --- /dev/null +++ b/data/scripts/actions/others/windows.lua @@ -0,0 +1,100 @@ +local windows = { + --[itemid] = {toItemid} + [5303] = {6448}, -- white stone wall window + [5304] = {6449}, -- white stone wall window + [6438] = {6436}, -- framework window + [6436] = {6438}, -- framework window + [6439] = {6437}, -- framework window + [6437] = {6439}, -- framework window + [6442] = {6440}, -- brick window + [6440] = {6442}, -- brick window + [6443] = {6441}, -- brick window + [6441] = {6443}, -- brick window + [6446] = {6444}, -- stone window + [6444] = {6446}, -- stone window + [6447] = {6445}, -- stone window + [6445] = {6447}, -- stone window + [6448] = {5303}, -- marble window + [6449] = {5304}, -- marble window + [6452] = {6450}, -- tree window + [6450] = {6452}, -- tree window + [6453] = {6451}, -- tree window + [6451] = {6453}, -- tree window + [6456] = {6454}, -- sandstone window + [6454] = {6456}, -- sandstone window + [6457] = {6455}, -- sandstone window + [6455] = {6457}, -- sandstone window + [6460] = {6458}, -- bamboo window + [6458] = {6460}, -- bamboo window + [6461] = {6459}, -- bamboo window + [6459] = {6461}, -- bamboo window + [6464] = {6462}, -- sandstone window + [6462] = {6464}, -- sandstone window + [6465] = {6463}, -- sandstone window + [6463] = {6465}, -- sandstone window + [6468] = {6466}, -- stone window + [6466] = {6468}, -- stone window + [6469] = {6467}, -- stone window + [6467] = {6469}, -- stone window + [6472] = {6470}, -- wooden window + [6470] = {6472}, -- wooden window + [6473] = {6471}, -- wooden window + [6471] = {6473}, -- wooden window + [6790] = {6788}, -- fur wall window + [6788] = {6790}, -- fur wall window + [6791] = {6789}, -- fur wall window + [6789] = {6791}, -- fur wall window + [7027] = {7025}, -- nordic wall window + [7025] = {7027}, -- nordic wall window + [7028] = {7026}, -- nordic wall window + [7026] = {7028}, -- nordic wall window + [7031] = {7029}, -- ice wall window + [7029] = {7031}, -- ice wall window + [7032] = {7030}, -- ice wall window + [7030] = {7032}, -- ice wall window + [10264] = {10266}, -- framework window + [10266] = {10264}, -- framework window + [10265] = {10267}, -- framework window + [10267] = {10265}, -- framework window + [10488] = {10490}, -- limestone window + [10490] = {10488}, -- limestone window + [10489] = {10491}, -- limestone window + [10491] = {10489}, -- limestone window +} + +local window = Action() + +function window.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local window = windows[item:getId()] + if not window then + return false + end + + local tile = Tile(fromPosition) + local house = tile and tile:getHouse() + if not house then + fromPosition.y = fromPosition.y - 1 + tile = Tile(fromPosition) + house = tile and tile:getHouse() + if not house then + fromPosition.y = fromPosition.y + 1 + fromPosition.x = fromPosition.x - 1 + tile = Tile(fromPosition) + house = tile and tile:getHouse() + end + end + + if house and player:getTile():getHouse() ~= house and player:getAccountType() < ACCOUNT_TYPE_GAMEMASTER then + return false + end + + player:addAchievementProgress("Do Not Disturb", 100) + player:addAchievementProgress("Let the Sunshine In", 100) + item:transform(window[1]) + return true +end + +for k, v in pairs(windows) do + window:id(k) +end +window:register() diff --git a/data/scripts/actions/tools/check_bless.lua b/data/scripts/actions/tools/check_bless.lua new file mode 100644 index 0000000..df7ecee --- /dev/null +++ b/data/scripts/actions/tools/check_bless.lua @@ -0,0 +1,25 @@ +local blessings = { + "Spiritual Shielding", + "Embrace of Tibia", + "Fire of the Suns", + "Spark of the Phoenix", + "Wisdom of Solitude", + "Twist of Fate" +} + +local checkBless = Action() + +function checkBless.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local message = {"Received blessings:"} + for i, blessing in pairs(blessings) do + if player:hasBlessing(i) then + message[#message + 1] = blessing + end + end + + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, #message == 1 and "No blessings received." or table.concat(message, '\n')) + return true +end + +checkBless:id(12424, 6561) +checkBless:register() diff --git a/data/scripts/actions/tools/claw_of_the_noxious_spawn.lua b/data/scripts/actions/tools/claw_of_the_noxious_spawn.lua new file mode 100644 index 0000000..bce81b7 --- /dev/null +++ b/data/scripts/actions/tools/claw_of_the_noxious_spawn.lua @@ -0,0 +1,31 @@ +local cursed = Condition(CONDITION_CURSED) +cursed:setParameter(CONDITION_PARAM_DELAYED, true) -- condition will delay the first damage from when it's added +cursed:setParameter(CONDITION_PARAM_MINVALUE, -800) -- minimum damage the condition can do at total +cursed:setParameter(CONDITION_PARAM_MAXVALUE, -1200) -- maximum damage +cursed:setParameter(CONDITION_PARAM_STARTVALUE, -1) -- the damage the condition will do on the first hit +cursed:setParameter(CONDITION_PARAM_TICKINTERVAL, 4000) -- delay between damages +cursed:setParameter(CONDITION_PARAM_FORCEUPDATE, true) -- re-update condition when adding it(ie. min/max value) + +local clawOfTheNoxiousSpawn = Action() + +function clawOfTheNoxiousSpawn.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if item == player:getSlotItem(CONST_SLOT_RING) then + if math.random(100) <= 5 then + player:addCondition(cursed) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are cursed by The Noxious Spawn!") + item:transform(10312) + item:decay() + player:getPosition():sendMagicEffect(CONST_ME_MAGIC_RED) + else + player:removeCondition(CONDITION_POISON) + item:transform(10311) + item:decay() + player:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) + end + return true + end + return false +end + +clawOfTheNoxiousSpawn:id(10309) +clawOfTheNoxiousSpawn:register() diff --git a/data/scripts/actions/tools/firebug.lua b/data/scripts/actions/tools/firebug.lua new file mode 100644 index 0000000..abd15e8 --- /dev/null +++ b/data/scripts/actions/tools/firebug.lua @@ -0,0 +1,36 @@ +local fireBug = Action() + +function fireBug.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local chance = math.random(10) + if chance > 4 then -- Success 6% chance + if target.itemid == 7538 then -- Destroy spider webs/North - South + toPosition:sendMagicEffect(CONST_ME_HITBYFIRE) + target:transform(7544) + target:decay() + elseif target.itemid == 7539 then -- Destroy spider webs/East - West + toPosition:sendMagicEffect(CONST_ME_HITBYFIRE) + target:transform(7545) + target:decay() + elseif target.itemid == 5466 then -- Burn Sugar Cane + toPosition:sendMagicEffect(CONST_ME_FIREAREA) + target:transform(5465) + target:decay() + elseif target.itemid == 1485 then -- Light up empty coal basins + toPosition:sendMagicEffect(CONST_ME_HITBYFIRE) + target:transform(1484) + end + elseif chance == 2 then -- It removes the firebug 1% chance + item:remove(1) + toPosition:sendMagicEffect(CONST_ME_POFF) + elseif chance == 1 then -- It explodes on the user 1% chance + doTargetCombat(0, player, COMBAT_FIREDAMAGE, -5, -5, CONST_ME_HITBYFIRE) + player:say('OUCH!', TALKTYPE_MONSTER_SAY) + item:remove(1) + else + toPosition:sendMagicEffect(CONST_ME_POFF) -- It fails, but don't get removed 3% chance + end + return true +end + +fireBug:id(5468) +fireBug:register() diff --git a/data/scripts/actions/tools/juice_squeezer.lua b/data/scripts/actions/tools/juice_squeezer.lua new file mode 100644 index 0000000..689bf37 --- /dev/null +++ b/data/scripts/actions/tools/juice_squeezer.lua @@ -0,0 +1,28 @@ +local juiceSquizer = Action() + +function juiceSquizer.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local fruits = { + 2673, -- pear + 2674, -- red apple + 2675, -- orange + 2676, -- banana + 2677, -- blueberry + 2678, -- coconut + 2679, -- cherry + 2680, -- strawberry + 2681, -- grapes + 2682, -- melon + 5097, -- mango + 8839, -- plum + 8840, -- raspberry + 8841 -- lemon + } + if table.contains(fruits, target.itemid) and player:removeItem(2006, 1, 0) then + target:remove(1) + player:addItem(2006, target.itemid == 2678 and 14 or 21) -- if target is a coconut, create coconut milk, otherwise create fruit juice + return true + end +end + +juiceSquizer:id(5865) +juiceSquizer:register() diff --git a/data/scripts/actions/tools/rust_remover.lua b/data/scripts/actions/tools/rust_remover.lua new file mode 100644 index 0000000..2350d24 --- /dev/null +++ b/data/scripts/actions/tools/rust_remover.lua @@ -0,0 +1,129 @@ +local config = { + [9808] = { -- Rusty Armor (Common) + [1] = {id = 2464, chance = 6994}, -- Chain Armor + [2] = {id = 2483, chance = 3952}, -- Scale Armor + [3] = {id = 2465, chance = 1502}, -- Brass Armor + [4] = {id = 2463, chance = 197} -- Plate Armor + }, + [9809] = { -- Rusty Armor (Semi-rare) + [1] = {id = 2483, chance = 6437}, -- Scale Armor + [2] = {id = 2464, chance = 4606}, -- Chain Armor + [3] = {id = 2465, chance = 3029}, -- Brass Armor + [4] = {id = 2463, chance = 1559}, -- Plate Armor + [5] = {id = 2476, chance = 595}, -- Knight Armor + [6] = {id = 8891, chance = 283}, -- Paladin Armor + [7] = {id = 2487, chance = 49} -- Crown Armor + }, + [9810] = { -- Rusty Armor (Rare) + [1] = {id = 2465, chance = 6681}, -- Brass Armor + [2] = {id = 2463, chance = 3767}, -- Plate Armor + [3] = {id = 2476, chance = 1832}, -- Knight Armor + [4] = {id = 2487, chance = 177}, -- Crown Armor + [5] = {id = 8891, chance = 31}, -- Paladin Armor + [6] = {id = 2466, chance = 10} -- Golden Armor + }, + [9811] = { -- Rusty Legs (Common) + [1] = {id = 2648, chance = 6949}, -- Chain Legs + [2] = {id = 2468, chance = 3692}, -- Studded Legs + [3] = {id = 2478, chance = 1307}, -- Brass Legs + [4] = {id = 2647, chance = 133} -- Plate Legs + }, + [9812] = { -- Rusty Legs (Semi-Rare) + [1] = {id = 2468, chance = 5962}, -- Studded Legs + [2] = {id = 2648, chance = 4037}, -- Chain Legs + [3] = {id = 2478, chance = 2174}, -- Brass Legs + [4] = {id = 2647, chance = 1242}, -- Plate Legs + [5] = {id = 2477, chance = 186}, -- Knight Legs + }, + [9813] = { -- Rusty Legs (Rare) + [1] = {id = 2478, chance = 6500}, -- Brass Legs + [2] = {id = 2647, chance = 3800}, -- Plate Legs + [3] = {id = 2477, chance = 200}, -- Knight Legs + [4] = {id = 2488, chance = 52}, -- Crown Legs + [5] = {id = 2470, chance = 30} -- Golden Legs + }, + [9814] = { -- Heavily Rusted Shield + }, + [9815] = { -- Rusted Shield + }, + [9816] = { -- Slightly Rusted Shield + [1] = {id = 2510, chance = 3137}, -- Plate Shield + [2] = {id = 2532, chance = 2887}, -- Ancient Shield + [3] = {id = 7460, chance = 929}, -- Norse Shield + [4] = {id = 2519, chance = 23}, -- Crown Shield + [5] = {id = 2534, chance = 10} -- Vampire Shield + }, + [9820] = { -- Heavily Rusted Helmet + }, + [9821] = { -- Rusted Helmet + [1] = {id = 2460, chance = 2200}, -- Brass Helmet + [2] = {id = 2482, chance = 1870}, -- Studded Helmet + [3] = {id = 2459, chance = 1490}, -- Iron Helmet + [4] = {id = 2457, chance = 1010}, -- Steel Helmet + [5] = {id = 2491, chance = 190}, -- Crown Helmet + [6] = {id = 2497, chance = 10} -- Crusader Helmet + }, + [9822] = { -- Slightly Rusted Helmet + [1] = {id = 2459, chance = 3156}, -- Iron Helmet + [2] = {id = 2457, chance = 2976}, -- Steel Helmet + [3] = {id = 2491, chance = 963}, -- Crown Helmet + [4] = {id = 2497, chance = 210}, -- Crusader Helmet + [5] = {id = 2498, chance = 7} -- Royal Helmet + }, + [9817] = { -- Heavily Rusted Boots + }, + [9818] = { -- Rusted Boots + }, + [9819] = { -- Slightly Rusted Boots + }, +} + +local rustRemover = Action() + +function rustRemover.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local targetItem = config[target.itemid] + if not targetItem then + return true + end + + local randomChance = math.random(10000) + local index = false + + if targetItem[1].chance >= randomChance then -- implying first item in the table index always has the highest chance. + while not index do + randomIndex = math.random(#targetItem) + if targetItem[randomIndex].chance >= randomChance then + index = randomIndex + end + end + end + + if not index then + if table.contains({9808, 9809, 9810}, target.itemid) then + msg = "The armor was already damaged so badly that it broke when you tried to clean it." + end + if table.contains({9811, 9812, 9813}, target.itemid) then + msg = "The legs were already damaged so badly that they broke when you tried to clean them." + end + if table.contains({9814, 9815, 9816}, target.itemid) then + msg = "The shield was already damaged so badly that it broke when you tried to clean it." + end + if table.contains({9817, 9818, 9819}, target.itemid) then + msg = "The boots were already damaged so badly that they broke when you tried to clean them." + end + if table.contains({9820, 9821, 9822}, target.itemid) then + msg = "The helmet was already damaged so badly that it broke when you tried to clean it." + end + player:say(msg, TALKTYPE_MONSTER_SAY) + target:getPosition():sendMagicEffect(CONST_ME_BLOCKHIT) + target:remove() + else + target:transform(targetItem[index].id) + target:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) + player:addAchievementProgress("Polisher", 1000) + end + return item:remove(1) +end + +rustRemover:id(9930) +rustRemover:register() diff --git a/data/scripts/actions/tools/saw.lua b/data/scripts/actions/tools/saw.lua new file mode 100644 index 0000000..c9ed57c --- /dev/null +++ b/data/scripts/actions/tools/saw.lua @@ -0,0 +1,13 @@ +local saw = Action() + +function saw.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if target.itemid ~= 5901 then -- wood + return false + end + + target:transform(10033) -- wooden ties + return true +end + +saw:id(2558) +saw:register() diff --git a/data/scripts/actions/tools/skinning.lua b/data/scripts/actions/tools/skinning.lua new file mode 100644 index 0000000..5dc5e51 --- /dev/null +++ b/data/scripts/actions/tools/skinning.lua @@ -0,0 +1,153 @@ +local config = { + [5908] = { + -- Minotaurs + [3090] = {chance = 7000, newItem = 5878, after = 2831}, -- minotaur + [5969] = {chance = 7000, newItem = 5878, after = 2831}, -- minotaur, after being killed + [2871] = {chance = 7000, newItem = 5878, after = 2872}, -- minotaur archer + [5982] = {chance = 7000, newItem = 5878, after = 2872}, -- minotaur archer, after being killed + [2866] = {chance = 7000, newItem = 5878, after = 2867}, -- minotaur mage + [5981] = {chance = 7000, newItem = 5878, after = 2867}, -- minotaur mage, after being killed + [2876] = {chance = 7000, newItem = 5878, after = 2877}, -- minotaur guard + [5983] = {chance = 7000, newItem = 5878, after = 2877}, -- minotaur guard, after being killed + + -- Low Class Lizards + [4259] = {chance = 6000, newItem = 5876, after = 4260}, -- lizard sentinel + [6040] = {chance = 6000, newItem = 5876, after = 4260}, -- lizard sentinel, after being killed + [4262] = {chance = 6000, newItem = 5876, after = 4263}, -- lizard snakecharmer + [6041] = {chance = 6000, newItem = 5876, after = 4263}, -- lizard snakecharmer, after being killed + [4256] = {chance = 6000, newItem = 5876, after = 4257}, -- lizard templar + [4251] = {chance = 6000, newItem = 5876, after = 4257}, -- lizard templar, after being killed + + -- High Class Lizards + [11285] = {chance = 10000, newItem = 5876, after = 11286}, -- lizard chosen, + [11288] = {chance = 10000, newItem = 5876, after = 11286}, -- lizard chosen, after being killed + [11277] = {chance = 10000, newItem = 5876, after = 11278}, -- lizard dragon priest + [11280] = {chance = 10000, newItem = 5876, after = 11278}, -- lizard dragon priest, after being killed + [11269] = {chance = 10000, newItem = 5876, after = 11270}, -- lizard high guard + [11272] = {chance = 10000, newItem = 5876, after = 11270}, -- lizard high guard, after being killed + [11281] = {chance = 10000, newItem = 5876, after = 11282}, -- lizard zaogun + [11284] = {chance = 10000, newItem = 5876, after = 11282}, -- lizard zaogun, after being killed + + -- Dragon + [3104] = {chance = 5000, newItem = 5877, after = 3105}, + [5973] = {chance = 5000, newItem = 5877, after = 3105}, -- after being killed + + -- Dragon Lord + [2881] = {chance = 5000, newItem = 5948, after = 2882}, + [5984] = {chance = 5000, newItem = 5948, after = 2882}, -- after being killed + + -- Behemoth + [2931] = {chance = 10000, newItem = 5893, after = 2932}, + [5999] = {chance = 10000, newItem = 5893, after = 2932}, -- after being killed + + -- Bone Beast + [3031] = {chance = 6000, newItem = 5925, after = 3032}, + [6030] = {chance = 6000, newItem = 5925, after = 3032}, -- after being killed + + -- Piece of Marble Rock + [11343] = { + {chance = 530, newItem = 11346, desc = "This little figurine of a goddess was masterfully sculpted by |PLAYERNAME|."}, + {chance = 9600, newItem = 11345, desc = "This little figurine made by |PLAYERNAME| has some room for improvement."}, + {chance = 24000, newItem = 11344, desc = "This shoddy work was made by |PLAYERNAME|."} + }, + + -- Ice Cube + [7441] = {chance = 22000, newItem = 7442}, + [7442] = {chance = 4800, newItem = 7444}, + [7444] = {chance = 900, newItem = 7445}, + [7445] = {chance = 40, newItem = 7446}, + }, + [5942] = { + -- Demon + [2916] = {chance = 3000, newItem = 5906, after = 2917}, + [5995] = {chance = 3000, newItem = 5906, after = 2917}, -- after being killed + + -- Vampires + [2956] = {chance = 6000, newItem = 5905, after = 2957}, -- vampire + [6006] = {chance = 6000, newItem = 5905, after = 2957}, -- vampire, after being killed + [9654] = {chance = 6000, newItem = 5905, after = 9658}, -- vampire bride + [9660] = {chance = 6000, newItem = 5905, after = 9658}, -- vampire bride, after being killed + } +} + +local skinning = Action() + +function skinning.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local skin = config[item.itemid][target.itemid] + if not skin then + player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return true + end + local randomChance = math.random(1, 100000) + local effect = CONST_ME_MAGIC_GREEN + local transform = true + if type(skin[1]) == "table" then + local added = false + for _, skinChild in ipairs(skin) do + if randomChance <= skinChild.chance then + if target.itemid == 11343 then + local marble = player:addItem(skinChild.newItem, skinChild.amount or 1) + if marble then + marble:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, skinChild.desc:gsub("|PLAYERNAME|", player:getName())) + end + if skinChild.newItem == 11346 then + player:addAchievement("Marblelous") + player:addAchievementProgress("Marble Madness", 5) + end + effect = CONST_ME_HITAREA + target:remove() + added = true + else + target:transform(skinChild.newItem, skinChild.amount or 1) + effect = CONST_ME_HITAREA + added = true + end + break + end + end + + if not added and target.itemid == 11343 then + effect = CONST_ME_HITAREA + player:say("Your attempt at shaping that marble rock failed miserably.", TALKTYPE_MONSTER_SAY) + transform = false + target:remove() + end + elseif randomChance <= skin.chance then + if table.contains({7441, 7442, 7444, 7445}, target.itemid) then + if skin.newItem == 7446 then + player:addAchievement("Ice Sculptor") + player:addAchievementProgress("Cold as Ice", 10) + end + target:transform(skin.newItem, 1) + effect = CONST_ME_HITAREA + else + if table.contains({5906, 5905}, skin.newItem) then + player:addAchievementProgress("Ashes to Dust", 500) + else + player:addAchievementProgress("Skin-Deep", 500) + end + player:addItem(skin.newItem, skin.amount or 1) + end + else + if table.contains({7441, 7442, 7444, 7445}, target.itemid) then + player:say("The attempt of sculpting failed miserably.", TALKTYPE_MONSTER_SAY) + effect = CONST_ME_HITAREA + target:remove() + else + effect = CONST_ME_BLOCKHIT + end + end + if transform then + target:transform(skin.after or target:getType():getDecayId() or target.itemid + 1) + else + target:remove() + end + if toPosition.x == CONTAINER_POSITION then + toPosition = player:getPosition() + end + toPosition:sendMagicEffect(effect) + return true +end + +skinning:id(5908, 5942) +skinning:register() diff --git a/data/scripts/creaturescripts/gs_wyda_death.lua b/data/scripts/creaturescripts/gs_wyda_death.lua new file mode 100644 index 0000000..35709f6 --- /dev/null +++ b/data/scripts/creaturescripts/gs_wyda_death.lua @@ -0,0 +1,11 @@ +local creatureevent = CreatureEvent("GiantSpiderWyda") + +function creatureevent.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified) + creature:say("It seems this was just an illusion.", TALKTYPE_MONSTER_SAY) + if mostDamageKiller:isPlayer() then + mostDamageKiller:addAchievement("Someone's Bored") + end + return true +end + +creatureevent:register() diff --git a/data/scripts/creaturescripts/scarab_death.lua b/data/scripts/creaturescripts/scarab_death.lua new file mode 100644 index 0000000..3de5f01 --- /dev/null +++ b/data/scripts/creaturescripts/scarab_death.lua @@ -0,0 +1,11 @@ +local creatureevent = CreatureEvent("ScarabDeath") + +function creatureevent.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified) + if math.random(100) < 10 then + Game.createMonster("Scorpion", creature:getPosition()) + creature:say("Horestis' curse spawns a vengeful scorpion from the body!", TALKTYPE_MONSTER_SAY) + end + return true +end + +creatureevent:register() diff --git a/data/scripts/eventcallbacks/monster/default_onDropLoot.lua b/data/scripts/eventcallbacks/monster/default_onDropLoot.lua new file mode 100644 index 0000000..1f0aac8 --- /dev/null +++ b/data/scripts/eventcallbacks/monster/default_onDropLoot.lua @@ -0,0 +1,39 @@ +local ec = EventCallback + +ec.onDropLoot = function(self, corpse) + if configManager.getNumber(configKeys.RATE_LOOT) == 0 then + return + end + + local player = Player(corpse:getCorpseOwner()) + local mType = self:getType() + if not player or player:getStamina() > 840 then + local monsterLoot = mType:getLoot() + for i = 1, #monsterLoot do + local item = corpse:createLootItem(monsterLoot[i]) + if not item then + print('[Warning] DropLoot:', 'Could not add loot item to corpse.') + end + end + + if player then + local text = ("Loot of %s: %s"):format(mType:getNameDescription(), corpse:getContentDescription()) + local party = player:getParty() + if party then + party:broadcastPartyLoot(text) + else + player:sendTextMessage(MESSAGE_INFO_DESCR, text) + end + end + else + local text = ("Loot of %s: nothing (due to low stamina)"):format(mType:getNameDescription()) + local party = player:getParty() + if party then + party:broadcastPartyLoot(text) + else + player:sendTextMessage(MESSAGE_INFO_DESCR, text) + end + end +end + +ec:register() diff --git a/data/scripts/eventcallbacks/player/default_onLook.lua b/data/scripts/eventcallbacks/player/default_onLook.lua new file mode 100644 index 0000000..f7187b7 --- /dev/null +++ b/data/scripts/eventcallbacks/player/default_onLook.lua @@ -0,0 +1,56 @@ +local ec = EventCallback + +ec.onLook = function(self, thing, position, distance, description) + local description = "You see " .. thing:getDescription(distance) + if self:getGroup():getAccess() then + if thing:isItem() then + description = string.format("%s\nItem ID: %d", description, thing:getId()) + + local actionId = thing:getActionId() + if actionId ~= 0 then + description = string.format("%s, Action ID: %d", description, actionId) + end + + local uniqueId = thing:getAttribute(ITEM_ATTRIBUTE_UNIQUEID) + if uniqueId > 0 and uniqueId < 65536 then + description = string.format("%s, Unique ID: %d", description, uniqueId) + end + + local itemType = thing:getType() + + local transformEquipId = itemType:getTransformEquipId() + local transformDeEquipId = itemType:getTransformDeEquipId() + if transformEquipId ~= 0 then + description = string.format("%s\nTransforms to: %d (onEquip)", description, transformEquipId) + elseif transformDeEquipId ~= 0 then + description = string.format("%s\nTransforms to: %d (onDeEquip)", description, transformDeEquipId) + end + + local decayId = itemType:getDecayId() + if decayId ~= -1 then + description = string.format("%s\nDecays to: %d", description, decayId) + end + elseif thing:isCreature() then + local str = "%s\nHealth: %d / %d" + if thing:isPlayer() and thing:getMaxMana() > 0 then + str = string.format("%s, Mana: %d / %d", str, thing:getMana(), thing:getMaxMana()) + end + description = string.format(str, description, thing:getHealth(), thing:getMaxHealth()) .. "." + end + + local position = thing:getPosition() + description = string.format( + "%s\nPosition: %d, %d, %d", + description, position.x, position.y, position.z + ) + + if thing:isCreature() then + if thing:isPlayer() then + description = string.format("%s\nIP: %s.", description, Game.convertIpToString(thing:getIp())) + end + end + end + return description +end + +ec:register() diff --git a/data/scripts/eventcallbacks/player/default_onLookInBattleList.lua b/data/scripts/eventcallbacks/player/default_onLookInBattleList.lua new file mode 100644 index 0000000..8ec7d21 --- /dev/null +++ b/data/scripts/eventcallbacks/player/default_onLookInBattleList.lua @@ -0,0 +1,25 @@ +local ec = EventCallback + +ec.onLookInBattleList = function(self, creature, distance) + local description = "You see " .. creature:getDescription(distance) + if self:getGroup():getAccess() then + local str = "%s\nHealth: %d / %d" + if creature:isPlayer() and creature:getMaxMana() > 0 then + str = string.format("%s, Mana: %d / %d", str, creature:getMana(), creature:getMaxMana()) + end + description = string.format(str, description, creature:getHealth(), creature:getMaxHealth()) .. "." + + local position = creature:getPosition() + description = string.format( + "%s\nPosition: %d, %d, %d", + description, position.x, position.y, position.z + ) + + if creature:isPlayer() then + description = string.format("%s\nIP: %s", description, Game.convertIpToString(creature:getIp())) + end + end + return description +end + +ec:register() diff --git a/data/scripts/eventcallbacks/player/default_onMoveItem.lua b/data/scripts/eventcallbacks/player/default_onMoveItem.lua new file mode 100644 index 0000000..b3b9bd0 --- /dev/null +++ b/data/scripts/eventcallbacks/player/default_onMoveItem.lua @@ -0,0 +1,33 @@ +local ec = EventCallback + +ec.onMoveItem = function(self, item, count, fromPosition, toPosition, fromCylinder, toCylinder) + if toPosition.x ~= CONTAINER_POSITION then + return true + end + + if item:getTopParent() == self and bit.band(toPosition.y, 0x40) == 0 then + local itemType, moveItem = ItemType(item:getId()) + if bit.band(itemType:getSlotPosition(), SLOTP_TWO_HAND) ~= 0 and toPosition.y == CONST_SLOT_LEFT then + moveItem = self:getSlotItem(CONST_SLOT_RIGHT) + elseif itemType:getWeaponType() == WEAPON_SHIELD and toPosition.y == CONST_SLOT_RIGHT then + moveItem = self:getSlotItem(CONST_SLOT_LEFT) + if moveItem and bit.band(ItemType(moveItem:getId()):getSlotPosition(), SLOTP_TWO_HAND) == 0 then + return true + end + end + + if moveItem then + local parent = item:getParent() + if parent:isContainer() and parent:getSize() == parent:getCapacity() then + self:sendTextMessage(MESSAGE_STATUS_SMALL, Game.getReturnMessage(RETURNVALUE_CONTAINERNOTENOUGHROOM)) + return false + else + return moveItem:moveTo(parent) + end + end + end + + return true +end + +ec:register() diff --git a/data/scripts/eventcallbacks/player/default_onReportBug.lua b/data/scripts/eventcallbacks/player/default_onReportBug.lua new file mode 100644 index 0000000..503c91b --- /dev/null +++ b/data/scripts/eventcallbacks/player/default_onReportBug.lua @@ -0,0 +1,31 @@ +local ec = EventCallback + +ec.onReportBug = function(self, message, position, category) + if self:getAccountType() == ACCOUNT_TYPE_NORMAL then + return false + end + + local name = self:getName() + local file = io.open("data/reports/bugs/" .. name .. " report.txt", "a") + + if not file then + self:sendTextMessage(MESSAGE_EVENT_DEFAULT, "There was an error when processing your report, please contact a gamemaster.") + return true + end + + io.output(file) + io.write("------------------------------\n") + io.write("Name: " .. name) + if category == BUG_CATEGORY_MAP then + io.write(" [Map position: " .. position.x .. ", " .. position.y .. ", " .. position.z .. "]") + end + local playerPosition = self:getPosition() + io.write(" [Player Position: " .. playerPosition.x .. ", " .. playerPosition.y .. ", " .. playerPosition.z .. "]\n") + io.write("Comment: " .. message .. "\n") + io.close(file) + + self:sendTextMessage(MESSAGE_EVENT_DEFAULT, "Your report has been sent to " .. configManager.getString(configKeys.SERVER_NAME) .. ".") + return true +end + +ec:register() diff --git a/data/scripts/eventcallbacks/player/default_onReportRuleViolation.lua b/data/scripts/eventcallbacks/player/default_onReportRuleViolation.lua new file mode 100644 index 0000000..99fd078 --- /dev/null +++ b/data/scripts/eventcallbacks/player/default_onReportRuleViolation.lua @@ -0,0 +1,41 @@ +local function hasPendingReport(name, targetName, reportType) + local f = io.open(string.format("data/reports/players/%s-%s-%d.txt", name, targetName, reportType), "r") + if f then + io.close(f) + return true + else + return false + end +end + +local ec = EventCallback + +ec.onReportRuleViolation = function(self, targetName, reportType, reportReason, comment, translation) + local name = self:getName() + if hasPendingReport(name, targetName, reportType) then + self:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your report is being processed.") + return + end + + local file = io.open(string.format("data/reports/players/%s-%s-%d.txt", name, targetName, reportType), "a") + if not file then + self:sendTextMessage(MESSAGE_EVENT_ADVANCE, "There was an error when processing your report, please contact a gamemaster.") + return + end + + io.output(file) + io.write("------------------------------\n") + io.write("Reported by: " .. name .. "\n") + io.write("Target: " .. targetName .. "\n") + io.write("Type: " .. reportType .. "\n") + io.write("Reason: " .. reportReason .. "\n") + io.write("Comment: " .. comment .. "\n") + if reportType ~= REPORT_TYPE_BOT then + io.write("Translation: " .. translation .. "\n") + end + io.write("------------------------------\n") + io.close(file) + self:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("Thank you for reporting %s. Your report will be processed by %s team as soon as possible.", targetName, configManager.getString(configKeys.SERVER_NAME))) +end + +ec:register() diff --git a/data/scripts/lib/create_functions.lua b/data/scripts/lib/create_functions.lua new file mode 100644 index 0000000..f34cc3b --- /dev/null +++ b/data/scripts/lib/create_functions.lua @@ -0,0 +1,2 @@ +createFunctions(MonsterType) -- creates get/set functions for MonsterType +createFunctions(Spell) -- creates get/set functions for Spell diff --git a/data/scripts/lib/defaults_move_event.lua b/data/scripts/lib/defaults_move_event.lua new file mode 100644 index 0000000..75191a6 --- /dev/null +++ b/data/scripts/lib/defaults_move_event.lua @@ -0,0 +1,25 @@ +-- default callbacks + +function defaultStepIn(creature, item, position, fromPosition) + return true +end + +function defaultStepOut(creature, item, position, fromPosition) + return true +end + +function defaultAddItem(moveitem, tileitem, pos) + return true +end + +function defaultRemoveItem(moveitem, tileitem, pos) + return true +end + +function defaultEquip(player, item, slot, isCheck) + return true +end + +function defaultDeEquip(player, item, slot, isCheck) + return true +end diff --git a/data/scripts/lib/event_callbacks.lua b/data/scripts/lib/event_callbacks.lua new file mode 100644 index 0000000..85a95cc --- /dev/null +++ b/data/scripts/lib/event_callbacks.lua @@ -0,0 +1,137 @@ +-- Creature +EVENT_CALLBACK_ONCHANGEOUTFIT = 1 +EVENT_CALLBACK_ONCHANGEMOUNT = 2 +EVENT_CALLBACK_ONAREACOMBAT = 3 +EVENT_CALLBACK_ONTARGETCOMBAT = 4 +EVENT_CALLBACK_ONHEAR = 5 +-- Party +EVENT_CALLBACK_ONJOIN = 6 +EVENT_CALLBACK_ONLEAVE = 7 +EVENT_CALLBACK_ONDISBAND = 8 +EVENT_CALLBACK_ONSHAREEXPERIENCE = 9 +-- Player +EVENT_CALLBACK_ONBROWSEFIELD = 10 +EVENT_CALLBACK_ONLOOK = 11 +EVENT_CALLBACK_ONLOOKINBATTLELIST = 12 +EVENT_CALLBACK_ONLOOKINTRADE = 13 +EVENT_CALLBACK_ONLOOKINSHOP = 14 +EVENT_CALLBACK_ONTRADEREQUEST = 15 +EVENT_CALLBACK_ONTRADEACCEPT = 16 +EVENT_CALLBACK_ONTRADECOMPLETED = 17 +EVENT_CALLBACK_ONMOVEITEM = 18 +EVENT_CALLBACK_ONITEMMOVED = 19 +EVENT_CALLBACK_ONMOVECREATURE = 20 +EVENT_CALLBACK_ONREPORTRULEVIOLATION = 21 +EVENT_CALLBACK_ONREPORTBUG = 22 +EVENT_CALLBACK_ONTURN = 23 +EVENT_CALLBACK_ONGAINEXPERIENCE = 24 +EVENT_CALLBACK_ONLOSEEXPERIENCE = 25 +EVENT_CALLBACK_ONGAINSKILLTRIES = 26 +EVENT_CALLBACK_ONWRAPITEM = 27 +-- Monster +EVENT_CALLBACK_ONDROPLOOT = 28 +EVENT_CALLBACK_ONSPAWN = 29 +-- last (for correct table counting) +EVENT_CALLBACK_LAST = EVENT_CALLBACK_ONSPAWN + +local callbacks = { + -- Creature + ["onChangeOutfit"] = EVENT_CALLBACK_ONCHANGEOUTFIT, + ["onChangeMount"] = EVENT_CALLBACK_ONCHANGEMOUNT, + ["onAreaCombat"] = EVENT_CALLBACK_ONAREACOMBAT, + ["onTargetCombat"] = EVENT_CALLBACK_ONTARGETCOMBAT, + ["onHear"] = EVENT_CALLBACK_ONHEAR, + -- Party + ["onJoin"] = EVENT_CALLBACK_ONJOIN, + ["onLeave"] = EVENT_CALLBACK_ONLEAVE, + ["onDisband"] = EVENT_CALLBACK_ONDISBAND, + ["onShareExperience"] = EVENT_CALLBACK_ONSHAREEXPERIENCE, + -- Player + ["onBrowseField"] = EVENT_CALLBACK_ONBROWSEFIELD, + ["onLook"] = EVENT_CALLBACK_ONLOOK, + ["onLookInBattleList"] = EVENT_CALLBACK_ONLOOKINBATTLELIST, + ["onLookInTrade"] = EVENT_CALLBACK_ONLOOKINTRADE, + ["onLookInShop"] = EVENT_CALLBACK_ONLOOKINSHOP, + ["onTradeRequest"] = EVENT_CALLBACK_ONTRADEREQUEST, + ["onTradeAccept"] = EVENT_CALLBACK_ONTRADEACCEPT, + ["onTradeCompleted"] = EVENT_CALLBACK_ONTRADECOMPLETED, + ["onMoveItem"] = EVENT_CALLBACK_ONMOVEITEM, + ["onItemMoved"] = EVENT_CALLBACK_ONITEMMOVED, + ["onMoveCreature"] = EVENT_CALLBACK_ONMOVECREATURE, + ["onReportRuleViolation"] = EVENT_CALLBACK_ONREPORTRULEVIOLATION, + ["onReportBug"] = EVENT_CALLBACK_ONREPORTBUG, + ["onTurn"] = EVENT_CALLBACK_ONTURN, + ["onGainExperience"] = EVENT_CALLBACK_ONGAINEXPERIENCE, + ["onLoseExperience"] = EVENT_CALLBACK_ONLOSEEXPERIENCE, + ["onGainSkillTries"] = EVENT_CALLBACK_ONGAINSKILLTRIES, + ["onWrapItem"] = EVENT_CALLBACK_ONWRAPITEM, + -- Monster + ["onDropLoot"] = EVENT_CALLBACK_ONDROPLOOT, + ["onSpawn"] = EVENT_CALLBACK_ONSPAWN +} + +local auxargs = { + [EVENT_CALLBACK_ONLOOK] = {[5] = 1}, + [EVENT_CALLBACK_ONLOOKINBATTLELIST] = {[4] = 1}, + [EVENT_CALLBACK_ONLOOKINTRADE] = {[5] = 1}, + [EVENT_CALLBACK_ONLOOKINSHOP] = {[4] = 1}, + [EVENT_CALLBACK_ONGAINEXPERIENCE] = {[3] = 1}, + [EVENT_CALLBACK_ONLOSEEXPERIENCE] = {[2] = 1}, + [EVENT_CALLBACK_ONGAINSKILLTRIES] = {[3] = 1} +} + +EventCallbackData = {} +hasEventCallback = function (type) + return #EventCallbackData[type] > 0 +end + +EventCallback = { + register = function (self, index) + if isScriptsInterface() then + local type, call = rawget(self, "type"), rawget(self, "call") + if type and call then + EventCallbackData[type][#EventCallbackData[type] + 1] = {call, tonumber(index) or 0} + table.sort(EventCallbackData[type], function (a, b) return a[2] < b[2] end) + return rawset(self, "type", nil) and rawset(self, "call", nil) + end + debugPrint("[Warning - EventCallback::register] is need to set up a callback before register.") + end + end, + clear = function (self) + EventCallbackData = {} + for i = 1, EVENT_CALLBACK_LAST do + EventCallbackData[i] = {} + end + end +} + +setmetatable(EventCallback, { + __index = function (self) return self end, + __newindex = function (self, k, v) + if isScriptsInterface() then + local ecType = callbacks[k] + if ecType then + if type(v) == "function" then + return rawset(self, "type", ecType) and rawset(self, "call", v) + end + debugPrint(string.format("[Warning - EventCallback::%s] a function is expected.", k)) + else + debugPrint(string.format("[Warning - EventCallback::%s] is not a valid callback.", k)) + end + end + end, + __call = function (self, type, ...) + local eventTable, ret = EventCallbackData[type] + local args, events = table.pack(...), #eventTable + for k, ev in pairs(eventTable) do + ret = {ev[1](unpack(args))} + if k == events or (ret[1] ~= nil and (ret[1] == false or table.contains({EVENT_CALLBACK_ONAREACOMBAT, EVENT_CALLBACK_ONTARGETCOMBAT}, type) and ret[1] ~= RETURNVALUE_NOERROR)) then + return unpack(ret) + end + for k, v in pairs(auxargs[type] or {}) do args[k] = ret[v] end + end + end + }) + +-- can't be overwritten on reloads +EventCallback:clear() diff --git a/data/scripts/lib/helper_constructors.lua b/data/scripts/lib/helper_constructors.lua new file mode 100644 index 0000000..1ff606f --- /dev/null +++ b/data/scripts/lib/helper_constructors.lua @@ -0,0 +1,52 @@ +local classes = {Action, CreatureEvent, Spell, TalkAction, MoveEvent, GlobalEvent, Weapon} + +for _, class in ipairs(classes) do + local MT = getmetatable(class) + local DefaultConstructor = MT.__call + + MT.__call = function(self, def, ...) + -- Backwards compatibility for default obj() constructor + if type(def) ~= "table" then + return DefaultConstructor(self, def, ...) + end + + local obj = nil + if def.init then + obj = DefaultConstructor(self, unpack(def.init)) + else + obj = DefaultConstructor(self) + end + + -- Call each method from definition table with the value as params + local hasCallback = false + + for methodName, value in pairs(def) do + -- Strictly check if a correct callback is passed + if methodName:sub(1, 2) == "on" and type(value) == "function" and rawget(class, methodName) then + hasCallback = true + end + + if methodName ~= "register" then + local method = rawget(self, methodName) + if method then + if type(value) == "table" then + method(obj, unpack(value)) + else + method(obj, value) + end + end + end + end + + -- Only register if callback has already been defined, otherwise defining afterwards will not work + if def.register then + if not hasCallback then + print("Warning: Event not registered due to there being no callback.") + else + obj:register() + end + end + + return obj + end +end diff --git a/data/scripts/lib/register_monster_type.lua b/data/scripts/lib/register_monster_type.lua new file mode 100644 index 0000000..2f3691a --- /dev/null +++ b/data/scripts/lib/register_monster_type.lua @@ -0,0 +1,514 @@ +registerMonsterType = {} +setmetatable(registerMonsterType, +{ + __call = + function(self, mtype, mask) + for _,parse in pairs(self) do + parse(mtype, mask) + end + end +}) + +MonsterType.register = function(self, mask) + return registerMonsterType(self, mask) +end + +registerMonsterType.name = function(mtype, mask) + if mask.name then + mtype:name(mask.name) + end +end +registerMonsterType.description = function(mtype, mask) + if mask.description then + mtype:nameDescription(mask.description) + end +end +registerMonsterType.experience = function(mtype, mask) + if mask.experience then + mtype:experience(mask.experience) + end +end +registerMonsterType.skull = function(mtype, mask) + if mask.skull then + mtype:skull(mask.skull) + end +end +registerMonsterType.outfit = function(mtype, mask) + if mask.outfit then + mtype:outfit(mask.outfit) + end +end +registerMonsterType.maxHealth = function(mtype, mask) + if mask.maxHealth then + mtype:maxHealth(mask.maxHealth) + mtype:health(math.min(mtype:health(), mask.maxHealth)) + end +end +registerMonsterType.health = function(mtype, mask) + if mask.health then + mtype:health(mask.health) + mtype:maxHealth(math.max(mask.health, mtype:maxHealth())) + end +end +registerMonsterType.runHealth = function(mtype, mask) + if mask.runHealth then + mtype:runHealth(mask.runHealth) + end +end +registerMonsterType.maxSummons = function(mtype, mask) + if mask.maxSummons then + mtype:maxSummons(mask.maxSummons) + end +end +registerMonsterType.race = function(mtype, mask) + if mask.race then + mtype:race(mask.race) + end +end +registerMonsterType.manaCost = function(mtype, mask) + if mask.manaCost then + mtype:manaCost(mask.manaCost) + end +end +registerMonsterType.speed = function(mtype, mask) + if mask.speed then + mtype:baseSpeed(mask.speed) + end +end +registerMonsterType.corpse = function(mtype, mask) + if mask.corpse then + mtype:corpseId(mask.corpse) + end +end +registerMonsterType.flags = function(mtype, mask) + if mask.flags then + if mask.flags.attackable ~= nil then + mtype:isAttackable(mask.flags.attackable) + end + if mask.flags.healthHidden ~= nil then + mtype:isHealthHidden(mask.flags.healthHidden) + end + if mask.flags.boss ~= nil then + mtype:isBoss(mask.flags.boss) + end + if mask.flags.challengeable ~= nil then + mtype:isChallengeable(mask.flags.challengeable) + end + if mask.flags.convinceable ~= nil then + mtype:isConvinceable(mask.flags.convinceable) + end + if mask.flags.summonable ~= nil then + mtype:isSummonable(mask.flags.summonable) + end + if mask.flags.ignoreSpawnBlock ~= nil then + mtype:isIgnoringSpawnBlock(mask.flags.ignoreSpawnBlock) + end + if mask.flags.illusionable ~= nil then + mtype:isIllusionable(mask.flags.illusionable) + end + if mask.flags.hostile ~= nil then + mtype:isHostile(mask.flags.hostile) + end + if mask.flags.pushable ~= nil then + mtype:isPushable(mask.flags.pushable) + end + if mask.flags.canPushItems ~= nil then + mtype:canPushItems(mask.flags.canPushItems) + end + if mask.flags.canPushCreatures ~= nil then + mtype:canPushCreatures(mask.flags.canPushCreatures) + end + -- if a monster can push creatures, + -- it should not be pushable + if mask.flags.canPushCreatures then + mtype:isPushable(false) + end + if mask.flags.targetDistance then + mtype:targetDistance(mask.flags.targetDistance) + end + if mask.flags.staticAttackChance then + mtype:staticAttackChance(mask.flags.staticAttackChance) + end + if mask.flags.canWalkOnEnergy ~= nil then + mtype:canWalkOnEnergy(mask.flags.canWalkOnEnergy) + end + if mask.flags.canWalkOnFire ~= nil then + mtype:canWalkOnFire(mask.flags.canWalkOnFire) + end + if mask.flags.canWalkOnPoison ~= nil then + mtype:canWalkOnPoison(mask.flags.canWalkOnPoison) + end + end +end +registerMonsterType.light = function(mtype, mask) + if mask.light then + mtype:light(mask.light.color or 0, mask.light.level or 0) + end +end +registerMonsterType.changeTarget = function(mtype, mask) + if mask.changeTarget then + if mask.changeTarget.chance then + mtype:changeTargetChance(mask.changeTarget.chance) + end + if mask.changeTarget.interval then + mtype:changeTargetSpeed(mask.changeTarget.interval) + end + end +end +registerMonsterType.voices = function(mtype, mask) + if type(mask.voices) == "table" then + local interval, chance + if mask.voices.interval then + interval = mask.voices.interval + end + if mask.voices.chance then + chance = mask.voices.chance + end + for k, v in pairs(mask.voices) do + if type(v) == "table" then + mtype:addVoice(v.text, interval, chance, v.yell) + end + end + end +end +registerMonsterType.summons = function(mtype, mask) + if type(mask.summons) == "table" then + for k, v in pairs(mask.summons) do + mtype:addSummon(v.name, v.interval, v.chance) + end + end +end +registerMonsterType.events = function(mtype, mask) + if type(mask.events) == "table" then + for k, v in pairs(mask.events) do + mtype:registerEvent(v) + end + end +end +registerMonsterType.loot = function(mtype, mask) + if type(mask.loot) == "table" then + local lootError = false + for _, loot in pairs(mask.loot) do + local parent = Loot() + if not parent:setId(loot.id) then + lootError = true + end + if loot.chance then + parent:setChance(loot.chance) + end + if loot.maxCount then + parent:setMaxCount(loot.maxCount) + end + if loot.aid or loot.actionId then + parent:setActionId(loot.aid or loot.actionId) + end + if loot.subType or loot.charges then + parent:setSubType(loot.subType or loot.charges) + end + if loot.text or loot.description then + parent:setDescription(loot.text or loot.description) + end + if loot.child then + for _, children in pairs(loot.child) do + local child = Loot() + if not child:setId(children.id) then + lootError = true + end + if children.chance then + child:setChance(children.chance) + end + if children.maxCount then + child:setMaxCount(children.maxCount) + end + if children.aid or children.actionId then + child:setActionId(children.aid or children.actionId) + end + if children.subType or children.charges then + child:setSubType(children.subType or children.charges) + end + if children.text or children.description then + child:setDescription(children.text or children.description) + end + parent:addChildLoot(child) + end + end + mtype:addLoot(parent) + end + if lootError then + print("[Warning - end] Monster: \"".. mtype:name() .. "\" loot could not correctly be load.") + end + end +end +registerMonsterType.elements = function(mtype, mask) + if type(mask.elements) == "table" then + for _, element in pairs(mask.elements) do + if element.type and element.percent then + mtype:addElement(element.type, element.percent) + end + end + end +end +registerMonsterType.immunities = function(mtype, mask) + if type(mask.immunities) == "table" then + for _, immunity in pairs(mask.immunities) do + if immunity.type and immunity.combat then + mtype:combatImmunities(immunity.type) + end + if immunity.type and immunity.condition then + mtype:conditionImmunities(immunity.type) + end + end + end +end +registerMonsterType.attacks = function(mtype, mask) + if type(mask.attacks) == "table" then + for _, attack in pairs(mask.attacks) do + local spell = MonsterSpell() + if attack.name then + if attack.name == "melee" then + spell:setType("melee") + if attack.attack and attack.skill then + spell:setAttackValue(attack.attack, attack.skill) + end + if attack.minDamage and attack.maxDamage then + spell:setCombatValue(attack.minDamage, attack.maxDamage) + end + if attack.interval then + spell:setInterval(attack.interval) + end + if attack.effect then + spell:setCombatEffect(attack.effect) + end + if attack.condition then + if attack.condition.type then + spell:setConditionType(attack.condition.type) + end + local startDamage = 0 + if attack.condition.startDamage then + startDamage = attack.condition.startDamage + end + if attack.condition.minDamage and attack.condition.maxDamage then + spell:setConditionDamage(attack.condition.minDamage, attack.condition.maxDamage, startDamage) + end + if attack.condition.duration then + spell:setConditionDuration(attack.condition.duration) + end + if attack.condition.interval then + spell:setConditionTickInterval(attack.condition.interval) + end + end + else + spell:setType(attack.name) + if attack.type then + if attack.name == "combat" then + spell:setCombatType(attack.type) + else + spell:setConditionType(attack.type) + end + end + if attack.interval then + spell:setInterval(attack.interval) + end + if attack.chance then + spell:setChance(attack.chance) + end + if attack.range then + spell:setRange(attack.range) + end + if attack.duration then + spell:setConditionDuration(attack.duration) + end + if attack.speed then + if type(attack.speed) ~= "table" then + spell:setConditionSpeedChange(attack.speed) + elseif type(attack.speed) == "table" then + if attack.speed.min and attack.speed.max then + spell:setConditionSpeedChange(attack.speed.min, attack.speed.max) + end + end + end + if attack.target then + spell:setNeedTarget(attack.target) + end + if attack.length then + spell:setCombatLength(attack.length) + end + if attack.spread then + spell:setCombatSpread(attack.spread) + end + if attack.radius then + spell:setCombatRadius(attack.radius) + end + if attack.minDamage and attack.maxDamage then + if attack.name == "combat" then + spell:setCombatValue(attack.minDamage, attack.maxDamage) + else + local startDamage = 0 + if attack.startDamage then + startDamage = attack.startDamage + end + spell:setConditionDamage(attack.minDamage, attack.maxDamage, startDamage) + end + end + if attack.effect then + spell:setCombatEffect(attack.effect) + end + if attack.shootEffect then + spell:setCombatShootEffect(attack.shootEffect) + end + if attack.name == "drunk" then + spell:setConditionType(CONDITION_DRUNK) + if attack.drunkenness then + spell:setConditionDrunkenness(attack.drunkenness) + end + end + end + elseif attack.script then + spell:setScriptName(attack.script) + if attack.interval then + spell:setInterval(attack.interval) + end + if attack.chance then + spell:setChance(attack.chance) + end + if attack.minDamage and attack.maxDamage then + spell:setCombatValue(attack.minDamage, attack.maxDamage) + end + if attack.target then + spell:setNeedTarget(attack.target) + end + if attack.direction then + spell:setNeedDirection(attack.direction) + end + end + mtype:addAttack(spell) + end + end +end +registerMonsterType.defenses = function(mtype, mask) + if type(mask.defenses) == "table" then + if mask.defenses.defense then + mtype:defense(mask.defenses.defense) + end + if mask.defenses.armor then + mtype:armor(mask.defenses.armor) + end + for _, defense in pairs(mask.defenses) do + if type(defense) == "table" then + local spell = MonsterSpell() + if defense.name then + if defense.name == "melee" then + spell:setType("melee") + if defense.attack and defense.skill then + spell:setAttackValue(defense.attack, defense.skill) + end + if defense.minDamage and defense.maxDamage then + spell:setCombatValue(defense.minDamage, defense.maxDamage) + end + if defense.interval then + spell:setInterval(defense.interval) + end + if defense.effect then + spell:setCombatEffect(defense.effect) + end + if defense.condition then + if defense.condition.type then + spell:setConditionType(defense.condition.type) + end + local startDamage = 0 + if defense.condition.startDamage then + startDamage = defense.condition.startDamage + end + if defense.condition.minDamage and defense.condition.maxDamage then + spell:setConditionDamage(defense.condition.minDamage, defense.condition.maxDamage, startDamage) + end + if defense.condition.duration then + spell:setConditionDuration(defense.condition.duration) + end + if defense.condition.interval then + spell:setConditionTickInterval(defense.condition.interval) + end + end + else + spell:setType(defense.name) + if defense.type then + if defense.name == "combat" then + spell:setCombatType(defense.type) + else + spell:setConditionType(defense.type) + end + end + if defense.interval then + spell:setInterval(defense.interval) + end + if defense.chance then + spell:setChance(defense.chance) + end + if defense.range then + spell:setRange(defense.range) + end + if defense.duration then + spell:setConditionDuration(defense.duration) + end + if defense.speed then + if type(defense.speed) ~= "table" then + spell:setConditionSpeedChange(defense.speed) + elseif type(defense.speed) == "table" then + if defense.speed.min and defense.speed.max then + spell:setConditionSpeedChange(defense.speed.min, defense.speed.max) + end + end + end + if defense.target then + spell:setNeedTarget(defense.target) + end + if defense.length then + spell:setCombatLength(defense.length) + end + if defense.spread then + spell:setCombatSpread(defense.spread) + end + if defense.radius then + spell:setCombatRadius(defense.radius) + end + if defense.minDamage and defense.maxDamage then + if defense.name == "combat" then + spell:setCombatValue(defense.minDamage, defense.maxDamage) + else + local startDamage = 0 + if defense.startDamage then + startDamage = defense.startDamage + end + spell:setConditionDamage(defense.minDamage, defense.maxDamage, startDamage) + end + end + if defense.effect then + spell:setCombatEffect(defense.effect) + end + if defense.shootEffect then + spell:setCombatShootEffect(defense.shootEffect) + end + end + elseif defense.script then + spell:setScriptName(defense.script) + if defense.interval then + spell:setInterval(defense.interval) + end + if defense.chance then + spell:setChance(defense.chance) + end + if defense.minDamage and defense.maxDamage then + spell:setCombatValue(defense.minDamage, defense.maxDamage) + end + if defense.target then + spell:setNeedTarget(defense.target) + end + if defense.direction then + spell:setNeedDirection(defense.direction) + end + end + mtype:addDefense(spell) + end + end + end +end diff --git a/data/scripts/movements/claw_of_the_noxious_spawn.lua b/data/scripts/movements/claw_of_the_noxious_spawn.lua new file mode 100644 index 0000000..0cdfd4f --- /dev/null +++ b/data/scripts/movements/claw_of_the_noxious_spawn.lua @@ -0,0 +1,17 @@ +local clawOfTheNoxiousSpawn = MoveEvent() + +function clawOfTheNoxiousSpawn.onEquip(player, item, slot, isCheck) + if isCheck == false then + if not Tile(player:getPosition()):hasFlag(TILESTATE_PROTECTIONZONE) then + doTargetCombat(0, player, COMBAT_PHYSICALDAMAGE, -150, -200, CONST_ME_DRAWBLOOD) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Ouch! The serpent claw stabbed you.") + return true + end + end + return true +end + +clawOfTheNoxiousSpawn:type("equip") +clawOfTheNoxiousSpawn:slot("ring") +clawOfTheNoxiousSpawn:id(10310) +clawOfTheNoxiousSpawn:register() diff --git a/data/scripts/movements/seeds.lua b/data/scripts/movements/seeds.lua new file mode 100644 index 0000000..4d60035 --- /dev/null +++ b/data/scripts/movements/seeds.lua @@ -0,0 +1,14 @@ +local moveevent = MoveEvent() + +function moveevent.onAddItem(moveitem, tileitem, position) + if moveitem:getId() == 7732 then -- seeds + tileitem:transform(7665) -- flower pot + tileitem:decay() + moveitem:remove(1) + position:sendMagicEffect(CONST_ME_MAGIC_GREEN) + end + return true +end + +moveevent:id(7655) -- empty flower pot +moveevent:register() diff --git a/data/scripts/movements/yellow_pillow.lua b/data/scripts/movements/yellow_pillow.lua new file mode 100644 index 0000000..6d98144 --- /dev/null +++ b/data/scripts/movements/yellow_pillow.lua @@ -0,0 +1,14 @@ +local yellowPillow = MoveEvent() +yellowPillow:type("stepin") + +function yellowPillow.onStepIn(player, item, position, fromPosition) + if not player or player:isInGhostMode() then + return true + end + player:say("Faaart!", TALKTYPE_MONSTER_SAY) + item:getPosition():sendMagicEffect(CONST_ME_POFF) + return true +end + +yellowPillow:id(8072) +yellowPillow:register() diff --git a/data/scripts/spells/#example.lua b/data/scripts/spells/#example.lua new file mode 100644 index 0000000..9ba6a17 --- /dev/null +++ b/data/scripts/spells/#example.lua @@ -0,0 +1,52 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_HEALING) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) +combat:setParameter(COMBAT_PARAM_TARGETCASTERORTOPMOST, true) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +function onGetFormulaValues(player, level, magicLevel) + local min = (level / 5) + (magicLevel * 3.2) + 20 + local max = (level / 5) + (magicLevel * 5.4) + 40 + return min, max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +local spell = Spell(SPELL_RUNE) + +function spell.onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end + +spell:name("test rune") +spell:runeId(2275) +spell:id(220) +spell:level(20) +spell:magicLevel(5) +spell:needTarget(true) +spell:isAggressive(false) +spell:allowFarUse(true) +spell:charges(25) +spell:vocation("sorcerer;true", "master sorcerer") +spell:register() + +local conjureRune = Spell(SPELL_INSTANT) + +function conjureRune.onCastSpell(creature, variant) + return creature:conjureItem(2260, 2275, 25) +end + +conjureRune:name("Test") +conjureRune:id(221) +conjureRune:words("adori mas test") +conjureRune:level(30) +conjureRune:mana(530) +conjureRune:group("support") +conjureRune:soul(3) +conjureRune:isAggressive(false) +conjureRune:cooldown(2000) +conjureRune:groupCooldown(2000) +conjureRune:needLearn(false) +conjureRune:vocation("sorcerer", "master sorcerer") +conjureRune:register() diff --git a/data/scripts/talkactions/position.lua b/data/scripts/talkactions/position.lua new file mode 100644 index 0000000..00f5e58 --- /dev/null +++ b/data/scripts/talkactions/position.lua @@ -0,0 +1,15 @@ +local talk = TalkAction("/pos", "!pos") + +function talk.onSay(player, words, param) + if player:getGroup():getAccess() and param ~= "" then + local split = param:split(",") + player:teleportTo(Position(split[1], split[2], split[3])) + else + local position = player:getPosition() + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Your current position is: " .. position.x .. ", " .. position.y .. ", " .. position.z .. ".") + end + return false +end + +talk:separator(" ") +talk:register() diff --git a/data/scripts/weapons/#example.lua b/data/scripts/weapons/#example.lua new file mode 100644 index 0000000..91ce910 --- /dev/null +++ b/data/scripts/weapons/#example.lua @@ -0,0 +1,80 @@ +--[[ + + Burst Arrow example + +]] +local area = createCombatArea({ + {1, 1, 1}, + {1, 3, 1}, + {1, 1, 1} +}) + +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_EXPLOSIONAREA) +combat:setParameter(COMBAT_PARAM_BLOCKARMOR, true) +combat:setFormula(COMBAT_FORMULA_SKILL, 0, 0, 1, 0) +combat:setArea(area) + +local burstarrow = Weapon(WEAPON_AMMO) + +burstarrow.onUseWeapon = function(player, variant) + if player:getSkull() == SKULL_BLACK then + return false + end + + return combat:execute(player, variant) +end + +burstarrow:id(2546) +burstarrow:attack(27) +burstarrow:shootType(CONST_ANI_BURSTARROW) +burstarrow:ammoType("arrow") +burstarrow:maxHitChance(100) +burstarrow:register() + +--[[ + + Wand of Vortex example + +]] +local wov = Weapon(WEAPON_WAND) +wov:id(2190) +wov:damage(8, 18) +wov:element("energy") +wov:level(7) +wov:mana(2) +wov:vocation("sorcerer", true, true) +wov:vocation("master sorcerer") +wov:register() + +--[[ + + Arbalest example + +]] +local arbalest = Weapon(WEAPON_DISTANCE) +arbalest:id(5803) +arbalest:slotType("two-handed") -- it's now a 2h weapon +arbalest:ammoType("bolt") +arbalest:range(6) +arbalest:attack(2) +arbalest:hitChance(2) +arbalest:level(75) +arbalest:wieldedUnproperly(true) +arbalest:register() + +--[[ + + Earth Barbarian Axe example + +]] +local eba = Weapon(WEAPON_AXE) +eba:id(7859) +eba:attack(23) +eba:defense(18, 1) +eba:extraElement(5, COMBAT_EARTHDAMAGE) +eba:charges(1000, true) -- showCharges = true +eba:action("removecharge") +eba:decayTo(2429) +eba:register() diff --git a/data/spells/scripts/house/edit_door_list.lua b/data/spells/scripts/house/edit_door.lua similarity index 58% rename from data/spells/scripts/house/edit_door_list.lua rename to data/spells/scripts/house/edit_door.lua index 77b7171..495dbbd 100644 --- a/data/spells/scripts/house/edit_door_list.lua +++ b/data/spells/scripts/house/edit_door.lua @@ -1,6 +1,9 @@ function onCastSpell(creature, variant) - local house = creature:getTile():getHouse() - local doorId = house and house:getDoorIdByPosition(variant:getPosition()) + local creaturePos = creature:getPosition() + creaturePos:getNextPosition(creature:getDirection()) + local tile = Tile(creaturePos) + local house = tile and tile:getHouse() + local doorId = house and house:getDoorIdByPosition(creaturePos) if not doorId or not house:canEditAccessList(doorId, creature) then creature:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) creature:getPosition():sendMagicEffect(CONST_ME_POFF) diff --git a/data/spells/scripts/house/edit_guest_list.lua b/data/spells/scripts/house/invite_guests.lua similarity index 100% rename from data/spells/scripts/house/edit_guest_list.lua rename to data/spells/scripts/house/invite_guests.lua diff --git a/data/spells/scripts/house/edit_subowner_list.lua b/data/spells/scripts/house/invite_subowners.lua similarity index 100% rename from data/spells/scripts/house/edit_subowner_list.lua rename to data/spells/scripts/house/invite_subowners.lua diff --git a/data/spells/scripts/house/kick.lua b/data/spells/scripts/house/kick_guest.lua similarity index 100% rename from data/spells/scripts/house/kick.lua rename to data/spells/scripts/house/kick_guest.lua diff --git a/data/spells/spells.xml b/data/spells/spells.xml index 9cbcd54..2df1643 100644 --- a/data/spells/spells.xml +++ b/data/spells/spells.xml @@ -1,6 +1,6 @@ - + @@ -552,10 +552,10 @@ - - - - + + + + @@ -601,4 +601,5 @@ + diff --git a/data/talkactions/scripts/TFS/add_skill.lua b/data/talkactions/scripts/add_skill.lua similarity index 58% rename from data/talkactions/scripts/TFS/add_skill.lua rename to data/talkactions/scripts/add_skill.lua index daca4aa..e2af99e 100644 --- a/data/talkactions/scripts/TFS/add_skill.lua +++ b/data/talkactions/scripts/add_skill.lua @@ -11,14 +11,15 @@ local function getSkillId(skillName) return SKILL_SHIELD elseif skillName:sub(1, 4) == "fish" then return SKILL_FISHING - else + elseif skillName:sub(1, 4) == "fist" then return SKILL_FIST + elseif skillName:sub(1, 1) == "m" then + return SKILL_MAGLEVEL + elseif skillName == "level" or skillName:sub(1, 1) == "l" or skillName:sub(1, 1) == "e" then + return SKILL_LEVEL end -end -local function getExpForLevel(level) - level = level - 1 - return ((50 * level * level * level) - (150 * level * level) + (400 * level)) / 3 + return nil end function onSay(player, words, param) @@ -47,16 +48,12 @@ function onSay(player, words, param) count = tonumber(split[3]) end - local ch = split[2]:sub(1, 1) - for i = 1, count do - if ch == "l" or ch == "e" then - target:addExperience(getExpForLevel(target:getLevel() + 1) - target:getExperience(), false) - elseif ch == "m" then - target:addManaSpent(target:getVocation():getRequiredManaSpent(target:getBaseMagicLevel() + 1) - target:getManaSpent()) - else - local skillId = getSkillId(split[2]) - target:addSkillTries(skillId, target:getVocation():getRequiredSkillTries(skillId, target:getSkillLevel(skillId) + 1) - target:getSkillTries(skillId)) - end + local skillId = getSkillId(split[2]) + if not skillId then + player:sendCancelMessage("Unknown skill.") + return false end + + target:addSkill(skillId, count) return false end diff --git a/data/talkactions/scripts/TFS/add_tutor.lua b/data/talkactions/scripts/add_tutor.lua similarity index 91% rename from data/talkactions/scripts/TFS/add_tutor.lua rename to data/talkactions/scripts/add_tutor.lua index c2dcde7..782904e 100644 --- a/data/talkactions/scripts/TFS/add_tutor.lua +++ b/data/talkactions/scripts/add_tutor.lua @@ -1,5 +1,5 @@ function onSay(player, words, param) - if player:getAccountType() <= ACCOUNT_TYPE_TUTOR then + if player:getAccountType() <= ACCOUNT_TYPE_SENIORTUTOR then return true end diff --git a/data/talkactions/scripts/animationeffect.lua b/data/talkactions/scripts/animationeffect.lua new file mode 100644 index 0000000..ba55558 --- /dev/null +++ b/data/talkactions/scripts/animationeffect.lua @@ -0,0 +1,29 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local effect = tonumber(param) + local position = player:getPosition() + local toPositionLow = {z = position.z} + local toPositionHigh = {z = position.z} + + toPositionLow.x = position.x - 7 + toPositionHigh.x = position.x + 7 + for i = -5, 5 do + toPositionLow.y = position.y + i + toPositionHigh.y = toPositionLow.y + position:sendDistanceEffect(toPositionLow, effect) + position:sendDistanceEffect(toPositionHigh, effect) + end + + toPositionLow.y = position.y - 5 + toPositionHigh.y = position.y + 5 + for i = -6, 6 do + toPositionLow.x = position.x + i + toPositionHigh.x = toPositionLow.x + position:sendDistanceEffect(toPositionLow, effect) + position:sendDistanceEffect(toPositionHigh, effect) + end + return false +end diff --git a/data/talkactions/scripts/attributes.lua b/data/talkactions/scripts/attributes.lua index 72d41da..2c8b21f 100644 --- a/data/talkactions/scripts/attributes.lua +++ b/data/talkactions/scripts/attributes.lua @@ -1,99 +1,48 @@ -local itemFunctions = { - ["actionid"] = { isActive = true, targetFunction = function (item, target) return item:setActionId(target) end }, - ["action"] = { isActive = true, targetFunction = function (item, target) return item:setActionId(target) end }, - ["aid"] = { isActive = true, targetFunction = function (item, target) return item:setActionId(target) end }, - ["description"] = { isActive = true, targetFunction = function (item, target) return item:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, target) end }, - ["desc"] = { isActive = true, targetFunction = function (item, target) return item:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, target) end }, - ["name"] = { isActive = true, targetFunction = function (item, target) return item:setAttribute(ITEM_ATTRIBUTE_NAME, target) end }, - ["remove"] = { isActive = true, targetFunction = function (item, target) return item:remove() end }, - ["decay"] = { isActive = true, targetFunction = function (item, target) return item:decay() end }, - ["transform"] = { isActive = true, targetFunction = function (item, target) return item:transform(target) end }, - ["clone"] = { isActive = true, targetFunction = function (item, target) return item:clone() end }, - ["attack"] = { isActive = true, targetFunction = function (item, target) return item:setAttribute(ITEM_ATTRIBUTE_ATTACK, target) end }, - ["defense"] = { isActive = true, targetFunction = function (item, target) return item:setAttribute(ITEM_ATTRIBUTE_DEFENSE, target) end }, - ["extradefense"] = { isActive = true, targetFunction = function (item, target) return item:setAttribute(ITEM_ATTRIBUTE_EXTRADEFENSE, target) end }, - ["armor"] = { isActive = true, targetFunction = function (item, target) return item:setAttribute(ITEM_ATTRIBUTE_ARMOR, target) end } -} +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end -local creatureFunctions = { - ["health"] = { isActive = true, targetFunction = function (creature, target) return creature:addHealth(target) end }, - ["mana"] = { isActive = true, targetFunction = function (creature, target) return creature:addMana(target) end }, - ["speed"] = { isActive = true, targetFunction = function (creature, target) return creature:changeSpeed(target) end }, - ["droploot"] = { isActive = true, targetFunction = function (creature, target) return creature:setDropLoot(target) end }, - ["skull"] = { isActive = true, targetFunction = function (creature, target) return creature:setSkull(target) end }, - ["direction"] = { isActive = true, targetFunction = function (creature, target) return creature:setDirection(target) end }, - ["maxHealth"] = { isActive = true, targetFunction = function (creature, target) return creature:setMaxHealth(target) end }, - ["say"] = { isActive = true, targetFunction = function (creature, target) creature:say(target, TALKTYPE_SAY) end } -} + local position = player:getPosition() + position:getNextPosition(player:getDirection()) -local playerFunctions = { - ["fyi"] = { isActive = true, targetFunction = function (player, target) return player:popupFYI(target) end }, - ["tutorial"] = { isActive = true, targetFunction = function (player, target) return player:sendTutorial(target) end }, - ["guildnick"] = { isActive = true, targetFunction = function (player, target) return player:setGuildNick(target) end }, - ["group"] = { isActive = true, targetFunction = function (player, target) return player:setGroup(Group(target)) end }, - ["vocation"] = { isActive = true, targetFunction = function (player, target) return player:setVocation(Vocation(target)) end }, - ["stamina"] = { isActive = true, targetFunction = function (player, target) return player:setStamina(target) end }, - ["town"] = { isActive = true, targetFunction = function (player, target) return player:setTown(Town(target)) end }, - ["balance"] = { isActive = true, targetFunction = function (player, target) return player:setBankBalance(target + player:getBankBalance()) end }, - ["save"] = { isActive = true, targetFunction = function (player, target) return target:save() end }, - ["type"] = { isActive = true, targetFunction = function (player, target) return player:setAccountType(target) end }, - ["skullTime"] = { isActive = true, targetFunction = function (player, target) return player:setSkullTime(target) end }, - ["maxMana"] = { isActive = true, targetFunction = function (player, target) return player:setMaxMana(target) end }, - ["addItem"] = { isActive = true, targetFunction = function (player, target) return player:addItem(target, 1) end }, - ["removeItem"] = { isActive = true, targetFunction = function (player, target) return player:removeItem(target, 1) end }, - ["premium"] = { isActive = true, targetFunction = function (player, target) return player:addPremiumDays(target) end } -} + local tile = Tile(position) + if not tile then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "There is no tile in front of you.") + return false + end -function onSay(player, words, param) - if(not player:getGroup():getAccess()) or player:getAccountType() < ACCOUNT_TYPE_GOD then - return true + local thing = tile:getTopVisibleThing(player) + if not thing then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "There is an empty tile in front of you.") + return false end - if(param == "") then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Command param required.") + local separatorPos = param:find(',') + if not separatorPos then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, string.format("Usage: %s attribute, value.", words)) return false end - local position = player:getPosition() - position:getNextPosition(player:getDirection(), 1) + local attribute = string.trim(param:sub(0, separatorPos - 1)) + local value = string.trim(param:sub(separatorPos + 1)) - local split = param:split(",") - local itemFunction, creatureFunction, playerFunction = itemFunctions[split[1]], creatureFunctions[split[1]], playerFunctions[split[1]] - if(itemFunction and itemFunction.isActive) then - local item = Tile(position):getTopVisibleThing(player) - if(not item or not item:isItem()) then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Item not found.") + if thing:isItem() then + local attributeId = Game.getItemAttributeByName(attribute) + if attributeId == ITEM_ATTRIBUTE_NONE then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Invalid attribute name.") return false end - if(itemFunction.targetFunction(item, split[2])) then - position:sendMagicEffect(CONST_ME_MAGIC_GREEN) - else - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You cannot add that attribute to this item.") - end - elseif(creatureFunction and creatureFunction.isActive) then - local creature = Tile(position):getTopCreature() - if(not creature or not creature:isCreature()) then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Creature not found.") - return false - end - if(creatureFunction.targetFunction(creature, split[2])) then - position:sendMagicEffect(CONST_ME_MAGIC_GREEN) - else - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You cannot add that attribute to this creature.") - end - elseif(playerFunction and playerFunction.isActive) then - local targetPlayer = Tile(position):getTopCreature() - if(not targetPlayer or not targetPlayer:getPlayer()) then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Player not found.") + + if not thing:setAttribute(attribute, value) then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Could not set attribute.") return false end - if(playerFunction.targetFunction(targetPlayer, split[2])) then - position:sendMagicEffect(CONST_ME_MAGIC_GREEN) - else - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You cannot add that attribute to this player.") - end + + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, string.format("Attribute %s set to: %s", attribute, thing:getAttribute(attributeId))) + position:sendMagicEffect(CONST_ME_MAGIC_GREEN) else - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Unknow attribute.") + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Thing in front of you is not supported.") + return false end - return false end diff --git a/data/talkactions/scripts/TFS/ban.lua b/data/talkactions/scripts/ban.lua similarity index 100% rename from data/talkactions/scripts/TFS/ban.lua rename to data/talkactions/scripts/ban.lua diff --git a/data/talkactions/scripts/TFS/broadcast.lua b/data/talkactions/scripts/broadcast.lua similarity index 100% rename from data/talkactions/scripts/TFS/broadcast.lua rename to data/talkactions/scripts/broadcast.lua diff --git a/data/talkactions/scripts/buyhouse.lua b/data/talkactions/scripts/buyhouse.lua index fad1cfa..8d146c3 100644 --- a/data/talkactions/scripts/buyhouse.lua +++ b/data/talkactions/scripts/buyhouse.lua @@ -1,9 +1,24 @@ +local config = { + level = 1, + onlyPremium = true +} + function onSay(player, words, param) local housePrice = configManager.getNumber(configKeys.HOUSE_PRICE) if housePrice == -1 then return true end + if player:getLevel() < config.level then + player:sendCancelMessage("You need level " .. config.level .. " or higher to buy a house.") + return false + end + + if config.onlyPremium and not player:isPremium() then + player:sendCancelMessage("You need a premium account.") + return false + end + local position = player:getPosition() position:getNextPosition(player:getDirection()) @@ -25,7 +40,7 @@ function onSay(player, words, param) end local price = house:getTileCount() * housePrice - if not player:removeMoney(price) then + if not player:removeTotalMoney(price) then player:sendCancelMessage("You do not have enough money.") return false end diff --git a/data/talkactions/scripts/TFS/buyprem.lua b/data/talkactions/scripts/buyprem.lua similarity index 90% rename from data/talkactions/scripts/TFS/buyprem.lua rename to data/talkactions/scripts/buyprem.lua index 63d5745..efb23a4 100644 --- a/data/talkactions/scripts/TFS/buyprem.lua +++ b/data/talkactions/scripts/buyprem.lua @@ -1,7 +1,7 @@ local config = { - days = 3, + days = 90, maxDays = 365, - price = 1000000 + price = 10000 } function onSay(player, words, param) @@ -10,7 +10,7 @@ function onSay(player, words, param) end if player:getPremiumDays() <= config.maxDays then - if player:removeMoney(config.price) then + if player:removeTotalMoney(config.price) then player:addPremiumDays(config.days) player:sendTextMessage(MESSAGE_INFO_DESCR, "You have bought " .. config.days .." days of premium account.") else diff --git a/data/talkactions/scripts/TFS/chameleon.lua b/data/talkactions/scripts/chameleon.lua similarity index 100% rename from data/talkactions/scripts/TFS/chameleon.lua rename to data/talkactions/scripts/chameleon.lua diff --git a/data/talkactions/scripts/changesex.lua b/data/talkactions/scripts/changesex.lua new file mode 100644 index 0000000..3b71fe0 --- /dev/null +++ b/data/talkactions/scripts/changesex.lua @@ -0,0 +1,19 @@ +local premiumDaysCost = 3 + +function onSay(player, words, param) + if player:getGroup():getAccess() then + player:setSex(player:getSex() == PLAYERSEX_FEMALE and PLAYERSEX_MALE or PLAYERSEX_FEMALE) + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have changed your sex.") + return false + end + + if player:getPremiumDays() >= premiumDaysCost then + player:removePremiumDays(premiumDaysCost) + player:setSex(player:getSex() == PLAYERSEX_FEMALE and PLAYERSEX_MALE or PLAYERSEX_FEMALE) + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have changed your sex for " .. premiumDaysCost .. " days of your premium account.") + else + player:sendCancelMessage("You do not have enough premium days, changing sex costs " .. premiumDaysCost .. " days of your premium account.") + player:getPosition():sendMagicEffect(CONST_ME_POFF) + end + return false +end diff --git a/data/talkactions/scripts/TFS/clean.lua b/data/talkactions/scripts/clean.lua similarity index 100% rename from data/talkactions/scripts/TFS/clean.lua rename to data/talkactions/scripts/clean.lua diff --git a/data/talkactions/scripts/TFS/closeserver.lua b/data/talkactions/scripts/closeserver.lua similarity index 100% rename from data/talkactions/scripts/TFS/closeserver.lua rename to data/talkactions/scripts/closeserver.lua diff --git a/data/talkactions/scripts/TFS/create_item.lua b/data/talkactions/scripts/create_item.lua similarity index 87% rename from data/talkactions/scripts/TFS/create_item.lua rename to data/talkactions/scripts/create_item.lua index 4b9a39e..6bf1a77 100644 --- a/data/talkactions/scripts/TFS/create_item.lua +++ b/data/talkactions/scripts/create_item.lua @@ -26,10 +26,14 @@ function onSay(player, words, param) return false end + local keyNumber = 0 local count = tonumber(split[2]) if count then if itemType:isStackable() then count = math.min(10000, math.max(1, count)) + elseif itemType:isKey() then + keyNumber = count + count = 1 elseif not itemType:isFluidContainer() then count = math.min(100, math.max(1, count)) else @@ -51,6 +55,9 @@ function onSay(player, words, param) item:decay() end else + if itemType:isKey() then + result:setAttribute(ITEM_ATTRIBUTE_ACTIONID, keyNumber) + end result:decay() end end diff --git a/data/talkactions/scripts/addondoll.lua b/data/talkactions/scripts/custom/addondoll.lua similarity index 100% rename from data/talkactions/scripts/addondoll.lua rename to data/talkactions/scripts/custom/addondoll.lua diff --git a/data/talkactions/scripts/alladdons.lua b/data/talkactions/scripts/custom/alladdons.lua similarity index 100% rename from data/talkactions/scripts/alladdons.lua rename to data/talkactions/scripts/custom/alladdons.lua diff --git a/data/talkactions/scripts/aol.lua b/data/talkactions/scripts/custom/aol.lua similarity index 100% rename from data/talkactions/scripts/aol.lua rename to data/talkactions/scripts/custom/aol.lua diff --git a/data/talkactions/scripts/auction_system.lua b/data/talkactions/scripts/custom/auction_system.lua similarity index 100% rename from data/talkactions/scripts/auction_system.lua rename to data/talkactions/scripts/custom/auction_system.lua diff --git a/data/talkactions/scripts/battlefield.lua b/data/talkactions/scripts/custom/battlefield.lua similarity index 100% rename from data/talkactions/scripts/battlefield.lua rename to data/talkactions/scripts/custom/battlefield.lua diff --git a/data/talkactions/scripts/bless.lua b/data/talkactions/scripts/custom/bless.lua similarity index 100% rename from data/talkactions/scripts/bless.lua rename to data/talkactions/scripts/custom/bless.lua diff --git a/data/talkactions/scripts/custom/buyhouse.lua b/data/talkactions/scripts/custom/buyhouse.lua new file mode 100644 index 0000000..09ed811 --- /dev/null +++ b/data/talkactions/scripts/custom/buyhouse.lua @@ -0,0 +1,51 @@ +local config = { + level = 150, + onlyPremium = false +} + +function onSay(player, words, param) + local housePrice = configManager.getNumber(configKeys.HOUSE_PRICE) + if housePrice == -1 then + return true + end + + if player:getLevel() < config.level then + player:sendCancelMessage("You need level " .. config.level .. " or higher to buy a house.") + return false + end + + if config.onlyPremium and not player:isPremium() then + player:sendCancelMessage("You need a premium account.") + return false + end + + local position = player:getPosition() + position:getNextPosition(player:getDirection()) + + local tile = Tile(position) + local house = tile and tile:getHouse() + if not house then + player:sendCancelMessage("You have to be looking at the door of the house you would like to buy.") + return false + end + + if house:getOwnerGuid() > 0 then + player:sendCancelMessage("This house already has an owner.") + return false + end + + if player:getHouse() then + player:sendCancelMessage("You are already the owner of a house.") + return false + end + + local price = house:getTileCount() * housePrice + if not player:removeTotalMoney(price) then + player:sendCancelMessage("You do not have enough money.") + return false + end + + house:setOwnerGuid(player:getGuid()) + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have successfully bought this house, be sure to have the money for the rent in the bank.") + return false +end diff --git a/data/talkactions/scripts/commands.lua b/data/talkactions/scripts/custom/commands.lua similarity index 100% rename from data/talkactions/scripts/commands.lua rename to data/talkactions/scripts/custom/commands.lua diff --git a/data/talkactions/scripts/duca.lua b/data/talkactions/scripts/custom/duca.lua similarity index 100% rename from data/talkactions/scripts/duca.lua rename to data/talkactions/scripts/custom/duca.lua diff --git a/data/talkactions/scripts/save.lua b/data/talkactions/scripts/custom/save.lua similarity index 100% rename from data/talkactions/scripts/save.lua rename to data/talkactions/scripts/custom/save.lua diff --git a/data/talkactions/scripts/serversave.lua b/data/talkactions/scripts/custom/serversave.lua similarity index 100% rename from data/talkactions/scripts/serversave.lua rename to data/talkactions/scripts/custom/serversave.lua diff --git a/data/talkactions/scripts/teleport_to_pos.lua b/data/talkactions/scripts/custom/teleport_to_pos.lua similarity index 100% rename from data/talkactions/scripts/teleport_to_pos.lua rename to data/talkactions/scripts/custom/teleport_to_pos.lua diff --git a/data/talkactions/scripts/zombie.lua b/data/talkactions/scripts/custom/zombie.lua similarity index 100% rename from data/talkactions/scripts/zombie.lua rename to data/talkactions/scripts/custom/zombie.lua diff --git a/data/talkactions/scripts/deathlist.lua b/data/talkactions/scripts/deathlist.lua new file mode 100644 index 0000000..3996b44 --- /dev/null +++ b/data/talkactions/scripts/deathlist.lua @@ -0,0 +1,62 @@ +local function getArticle(str) + return str:find("[AaEeIiOoUuYy]") == 1 and "an" or "a" +end + +local function getMonthDayEnding(day) + if day == "01" or day == "21" or day == "31" then + return "st" + elseif day == "02" or day == "22" then + return "nd" + elseif day == "03" or day == "23" then + return "rd" + else + return "th" + end +end + +local function getMonthString(m) + return os.date("%B", os.time{year = 1970, month = m, day = 1}) +end + +function onSay(player, words, param) + local resultId = db.storeQuery("SELECT `id`, `name` FROM `players` WHERE `name` = " .. db.escapeString(param)) + if resultId ~= false then + local targetGUID = result.getNumber(resultId, "id") + local targetName = result.getString(resultId, "name") + result.free(resultId) + local str = "" + local breakline = "" + + local resultId = db.storeQuery("SELECT `time`, `level`, `killed_by`, `is_player` FROM `player_deaths` WHERE `player_id` = " .. targetGUID .. " ORDER BY `time` DESC") + if resultId ~= false then + repeat + if str ~= "" then + breakline = "\n" + end + local date = os.date("*t", result.getNumber(resultId, "time")) + + local article = "" + local killed_by = result.getString(resultId, "killed_by") + if result.getNumber(resultId, "is_player") == 0 then + article = getArticle(killed_by) .. " " + killed_by = string.lower(killed_by) + end + + if date.day < 10 then date.day = "0" .. date.day end + if date.hour < 10 then date.hour = "0" .. date.hour end + if date.min < 10 then date.min = "0" .. date.min end + if date.sec < 10 then date.sec = "0" .. date.sec end + str = str .. breakline .. " " .. date.day .. getMonthDayEnding(date.day) .. " " .. getMonthString(date.month) .. " " .. date.year .. " " .. date.hour .. ":" .. date.min .. ":" .. date.sec .. " Died at Level " .. result.getNumber(resultId, "level") .. " by " .. article .. killed_by .. "." + until not result.next(resultId) + result.free(resultId) + end + + if str == "" then + str = "No deaths." + end + player:popupFYI("Deathlist for player, " .. targetName .. ".\n\n" .. str) + else + player:sendCancelMessage("A player with that name does not exist.") + end + return false +end diff --git a/data/talkactions/scripts/TFS/down.lua b/data/talkactions/scripts/down.lua similarity index 100% rename from data/talkactions/scripts/TFS/down.lua rename to data/talkactions/scripts/down.lua diff --git a/data/talkactions/scripts/TFS/force_raid.lua b/data/talkactions/scripts/force_raid.lua similarity index 100% rename from data/talkactions/scripts/TFS/force_raid.lua rename to data/talkactions/scripts/force_raid.lua diff --git a/data/talkactions/scripts/TFS/ghost.lua b/data/talkactions/scripts/ghost.lua similarity index 100% rename from data/talkactions/scripts/TFS/ghost.lua rename to data/talkactions/scripts/ghost.lua diff --git a/data/talkactions/scripts/TFS/hide.lua b/data/talkactions/scripts/hide.lua similarity index 100% rename from data/talkactions/scripts/TFS/hide.lua rename to data/talkactions/scripts/hide.lua diff --git a/data/talkactions/scripts/TFS/info.lua b/data/talkactions/scripts/info.lua similarity index 100% rename from data/talkactions/scripts/TFS/info.lua rename to data/talkactions/scripts/info.lua diff --git a/data/talkactions/scripts/TFS/ipban.lua b/data/talkactions/scripts/ipban.lua similarity index 100% rename from data/talkactions/scripts/TFS/ipban.lua rename to data/talkactions/scripts/ipban.lua diff --git a/data/talkactions/scripts/TFS/kick.lua b/data/talkactions/scripts/kick.lua similarity index 100% rename from data/talkactions/scripts/TFS/kick.lua rename to data/talkactions/scripts/kick.lua diff --git a/data/talkactions/scripts/TFS/kills.lua b/data/talkactions/scripts/kills.lua similarity index 100% rename from data/talkactions/scripts/TFS/kills.lua rename to data/talkactions/scripts/kills.lua diff --git a/data/talkactions/scripts/TFS/leavehouse.lua b/data/talkactions/scripts/leavehouse.lua similarity index 100% rename from data/talkactions/scripts/TFS/leavehouse.lua rename to data/talkactions/scripts/leavehouse.lua diff --git a/data/talkactions/scripts/TFS/looktype.lua b/data/talkactions/scripts/looktype.lua similarity index 54% rename from data/talkactions/scripts/TFS/looktype.lua rename to data/talkactions/scripts/looktype.lua index bc57bfb..05d677d 100644 --- a/data/talkactions/scripts/TFS/looktype.lua +++ b/data/talkactions/scripts/looktype.lua @@ -5,7 +5,16 @@ local invalidTypes = { 189, 190, 191, 411, 415, 424, 439, 440, 468, 469, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 501, 518, 519, 520, 524, 525, 536, 543, 549, 576, 581, 582, 597, 616, 623, 625, 638, 639, 640, 641, 642, 643, 645, - 646, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663 + 646, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 678, 700, + 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 713, 715, 718, 719, + 722, 723, 737, 741, 742, 743, 744, 748, 751, 752, 753, 754, 755, 756, 757, + 758, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, + 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, + 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, + 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, + 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, + 838, 839, 840, 841, 847, 864, 865, 866, 867, 871, 872, 880, 891, 892, 893, + 894, 895, 896, 897, 898 } function onSay(player, words, param) @@ -14,7 +23,7 @@ function onSay(player, words, param) end local lookType = tonumber(param) - if lookType >= 0 and lookType < 700 and not table.contains(invalidTypes, lookType) then + if lookType >= 0 and lookType < 903 and not table.contains(invalidTypes, lookType) then local playerOutfit = player:getOutfit() playerOutfit.lookType = lookType player:setOutfit(playerOutfit) diff --git a/data/talkactions/scripts/magiceffect.lua b/data/talkactions/scripts/magiceffect.lua new file mode 100644 index 0000000..51972c2 --- /dev/null +++ b/data/talkactions/scripts/magiceffect.lua @@ -0,0 +1,12 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local effect = tonumber(param) + if(effect ~= nil and effect > 0) then + player:getPosition():sendMagicEffect(effect) + end + + return false +end diff --git a/data/talkactions/scripts/TFS/mccheck.lua b/data/talkactions/scripts/mccheck.lua similarity index 100% rename from data/talkactions/scripts/TFS/mccheck.lua rename to data/talkactions/scripts/mccheck.lua diff --git a/data/talkactions/scripts/TFS/online.lua b/data/talkactions/scripts/online.lua similarity index 86% rename from data/talkactions/scripts/TFS/online.lua rename to data/talkactions/scripts/online.lua index 56879fa..db7cceb 100644 --- a/data/talkactions/scripts/TFS/online.lua +++ b/data/talkactions/scripts/online.lua @@ -1,12 +1,11 @@ local maxPlayersPerMessage = 10 function onSay(player, words, param) - local hasAccess = player:getGroup():getAccess() local players = Game.getPlayers() local onlineList = {} for _, targetPlayer in ipairs(players) do - if hasAccess or not targetPlayer:isInGhostMode() then + if player:canSeeCreature(targetPlayer) then table.insert(onlineList, ("%s [%d]"):format(targetPlayer:getName(), targetPlayer:getLevel())) end end diff --git a/data/talkactions/scripts/TFS/openserver.lua b/data/talkactions/scripts/openserver.lua similarity index 100% rename from data/talkactions/scripts/TFS/openserver.lua rename to data/talkactions/scripts/openserver.lua diff --git a/data/talkactions/scripts/TFS/owner.lua b/data/talkactions/scripts/owner.lua similarity index 100% rename from data/talkactions/scripts/TFS/owner.lua rename to data/talkactions/scripts/owner.lua diff --git a/data/talkactions/scripts/TFS/place_monster.lua b/data/talkactions/scripts/place_monster.lua similarity index 100% rename from data/talkactions/scripts/TFS/place_monster.lua rename to data/talkactions/scripts/place_monster.lua diff --git a/data/talkactions/scripts/TFS/place_npc.lua b/data/talkactions/scripts/place_npc.lua similarity index 100% rename from data/talkactions/scripts/TFS/place_npc.lua rename to data/talkactions/scripts/place_npc.lua diff --git a/data/talkactions/scripts/TFS/place_summon.lua b/data/talkactions/scripts/place_summon.lua similarity index 100% rename from data/talkactions/scripts/TFS/place_summon.lua rename to data/talkactions/scripts/place_summon.lua diff --git a/data/talkactions/scripts/position.lua b/data/talkactions/scripts/position.lua new file mode 100644 index 0000000..299ce6e --- /dev/null +++ b/data/talkactions/scripts/position.lua @@ -0,0 +1,10 @@ +function onSay(player, words, param) + if player:getGroup():getAccess() and param ~= "" then + local split = param:split(",") + player:teleportTo(Position(split[1], split[2], split[3])) + else + local position = player:getPosition() + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Your current position is: " .. position.x .. ", " .. position.y .. ", " .. position.z .. ".") + end + return false +end diff --git a/data/talkactions/scripts/TFS/reload.lua b/data/talkactions/scripts/reload.lua similarity index 90% rename from data/talkactions/scripts/TFS/reload.lua rename to data/talkactions/scripts/reload.lua index 7e98385..f11e9d5 100644 --- a/data/talkactions/scripts/TFS/reload.lua +++ b/data/talkactions/scripts/reload.lua @@ -73,6 +73,11 @@ function onSay(player, words, param) return false end + -- need to clear EventCallback.data or we end up having duplicated events on /reload scripts + if table.contains({RELOAD_TYPE_SCRIPTS, RELOAD_TYPE_ALL}, reloadType) then + EventCallback:clear() + end + Game.reload(reloadType) player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, string.format("Reloaded %s.", param:lower())) return false diff --git a/data/talkactions/scripts/TFS/remove_tutor.lua b/data/talkactions/scripts/remove_tutor.lua similarity index 94% rename from data/talkactions/scripts/TFS/remove_tutor.lua rename to data/talkactions/scripts/remove_tutor.lua index c233fbc..9f57efa 100644 --- a/data/talkactions/scripts/TFS/remove_tutor.lua +++ b/data/talkactions/scripts/remove_tutor.lua @@ -1,5 +1,5 @@ function onSay(player, words, param) - if player:getAccountType() <= ACCOUNT_TYPE_TUTOR then + if player:getAccountType() <= ACCOUNT_TYPE_SENIORTUTOR then return true end diff --git a/data/talkactions/scripts/TFS/removething.lua b/data/talkactions/scripts/removething.lua similarity index 100% rename from data/talkactions/scripts/TFS/removething.lua rename to data/talkactions/scripts/removething.lua diff --git a/data/talkactions/scripts/TFS/sellhouse.lua b/data/talkactions/scripts/sellhouse.lua similarity index 100% rename from data/talkactions/scripts/TFS/sellhouse.lua rename to data/talkactions/scripts/sellhouse.lua diff --git a/data/talkactions/scripts/TFS/serverinfo.lua b/data/talkactions/scripts/serverinfo.lua similarity index 100% rename from data/talkactions/scripts/TFS/serverinfo.lua rename to data/talkactions/scripts/serverinfo.lua diff --git a/data/talkactions/scripts/TFS/teleport_creature_here.lua b/data/talkactions/scripts/teleport_creature_here.lua similarity index 100% rename from data/talkactions/scripts/TFS/teleport_creature_here.lua rename to data/talkactions/scripts/teleport_creature_here.lua diff --git a/data/talkactions/scripts/TFS/teleport_home.lua b/data/talkactions/scripts/teleport_home.lua similarity index 100% rename from data/talkactions/scripts/TFS/teleport_home.lua rename to data/talkactions/scripts/teleport_home.lua diff --git a/data/talkactions/scripts/TFS/teleport_ntiles.lua b/data/talkactions/scripts/teleport_ntiles.lua similarity index 100% rename from data/talkactions/scripts/TFS/teleport_ntiles.lua rename to data/talkactions/scripts/teleport_ntiles.lua diff --git a/data/talkactions/scripts/TFS/teleport_to_creature.lua b/data/talkactions/scripts/teleport_to_creature.lua similarity index 100% rename from data/talkactions/scripts/TFS/teleport_to_creature.lua rename to data/talkactions/scripts/teleport_to_creature.lua diff --git a/data/talkactions/scripts/TFS/teleport_to_town.lua b/data/talkactions/scripts/teleport_to_town.lua similarity index 100% rename from data/talkactions/scripts/TFS/teleport_to_town.lua rename to data/talkactions/scripts/teleport_to_town.lua diff --git a/data/talkactions/scripts/TFS/unban.lua b/data/talkactions/scripts/unban.lua similarity index 100% rename from data/talkactions/scripts/TFS/unban.lua rename to data/talkactions/scripts/unban.lua diff --git a/data/talkactions/scripts/TFS/up.lua b/data/talkactions/scripts/up.lua similarity index 100% rename from data/talkactions/scripts/TFS/up.lua rename to data/talkactions/scripts/up.lua diff --git a/data/talkactions/scripts/TFS/uptime.lua b/data/talkactions/scripts/uptime.lua similarity index 100% rename from data/talkactions/scripts/TFS/uptime.lua rename to data/talkactions/scripts/uptime.lua diff --git a/data/talkactions/talkactions.xml b/data/talkactions/talkactions.xml index d0d7a78..3db3b8e 100644 --- a/data/talkactions/talkactions.xml +++ b/data/talkactions/talkactions.xml @@ -1,64 +1,74 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + + + + + + + + + - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + diff --git a/data/weapons/scripts/empty.lua b/data/weapons/scripts/empty.lua deleted file mode 100644 index 2c5f732..0000000 --- a/data/weapons/scripts/empty.lua +++ /dev/null @@ -1 +0,0 @@ --- Empty diff --git a/data/weapons/weapons.xml b/data/weapons/weapons.xml index 3ccbc04..702fae7 100644 --- a/data/weapons/weapons.xml +++ b/data/weapons/weapons.xml @@ -56,96 +56,74 @@ - - - - - - - - - - - - - - - - - - - - - - @@ -153,11 +131,9 @@ - - @@ -166,11 +142,9 @@ - - @@ -179,11 +153,9 @@ - - @@ -192,17 +164,14 @@ - - - @@ -210,40 +179,33 @@ - - - - - - - @@ -251,38 +213,31 @@ - - - - - - - @@ -292,11 +247,9 @@ - - @@ -305,11 +258,9 @@ - - @@ -318,11 +269,9 @@ - - @@ -331,11 +280,9 @@ - - @@ -352,11 +299,9 @@ - - @@ -367,26 +312,21 @@ - - - - - @@ -395,20 +335,16 @@ - - - - @@ -417,7 +353,6 @@ - @@ -427,7 +362,6 @@ - @@ -437,7 +371,6 @@ - @@ -447,53 +380,41 @@ - - - - - - - - - - - - diff --git a/data/world/house.xml b/data/world/house.xml index e5a6b86..364d675 100644 --- a/data/world/house.xml +++ b/data/world/house.xml @@ -1,2 +1,88 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/world/spawn.xml b/data/world/spawn.xml index 9a6fd0d..1c83cc7 100644 --- a/data/world/spawn.xml +++ b/data/world/spawn.xml @@ -1,8 +1,238 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -12,12 +242,122 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -50,6 +390,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -63,6 +439,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -82,6 +495,10 @@ + + + + @@ -97,51 +514,208 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + - - + + - - - - + + + + + - - - - + + + + - + - - - + + + + - - - + + + - - + + + - - - - - - - - - + + + + @@ -160,9 +734,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -177,6 +778,13 @@ + + + + + + + @@ -185,9 +793,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -206,6 +843,11 @@ + + + + + @@ -217,6 +859,12 @@ + + + + + + @@ -244,15 +892,25 @@ + + + + + + + + + + @@ -265,12 +923,25 @@ + + + + + + + + + + + + + @@ -281,116 +952,397 @@ + + + + + + + + - - - - - + + - - - - - + + - - - - - + + + - - - - + + - - - - + + - - - + + - - - + + - - - - + + - - - + + - - - - + + - - - + + - - + + + - - + + + - - + + - - + + - - + + - - - + + - - - - + + - - - - - - - - - - - - - - - - - + + - - - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -417,6 +1369,9 @@ + + + @@ -437,6 +1392,9 @@ + + + @@ -447,13 +1405,32 @@ + + + + + + + + + + + + + + + + + + + @@ -469,9 +1446,15 @@ + + + + + + @@ -499,6 +1482,9 @@ + + + @@ -519,6 +1505,9 @@ + + + @@ -550,6 +1539,9 @@ + + + @@ -559,6 +1551,15 @@ + + + + + + + + + @@ -596,6 +1597,9 @@ + + + @@ -611,6 +1615,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -630,27 +1667,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -659,10 +1770,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -704,28 +1887,28 @@ - - - - + + + + - - + + - - + + - - - + + + - - - - + + + + @@ -736,33 +1919,33 @@ - - - - + + + + - - + + - - - - - - - + + + - + + + + + - - + + @@ -770,31 +1953,34 @@ - - - - + + + + - - + + - - - - + + + + - - + + + + + @@ -804,34 +1990,64 @@ - - + + - - + + - - - - + - - + + + + + + + + + + + - + - + + + + + + + + + - - + + + + + + + - - + + + + + + + + + + + + + @@ -841,8 +2057,21 @@ - - + + + + + + + + + + + + + + + @@ -868,6 +2097,12 @@ + + + + + + @@ -880,28 +2115,89 @@ + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -911,6 +2207,9 @@ + + + @@ -920,12 +2219,12 @@ - - - + + + @@ -943,6 +2242,12 @@ + + + + + + @@ -955,17 +2260,33 @@ + + + + + + + + + + + + + + + + @@ -975,6 +2296,10 @@ + + + + @@ -984,6 +2309,10 @@ + + + + @@ -992,6 +2321,9 @@ + + + @@ -1002,15 +2334,32 @@ + + + - + + + + + + + + + + + + + + + @@ -1022,28 +2371,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1057,22 +2488,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1082,9 +2558,21 @@ + + + + + + + + + + + + @@ -1095,6 +2583,18 @@ + + + + + + + + + + + + @@ -1112,6 +2612,15 @@ + + + + + + + + + @@ -1120,6 +2629,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1129,13 +2665,34 @@ + + + + + + + + + + + + + + + + + + + + + @@ -1143,15 +2700,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1162,27 +2754,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1192,10 +2844,28 @@ + + + + + + + + + + + + + + + + + + @@ -1203,6 +2873,9 @@ + + + @@ -1211,10 +2884,25 @@ + + + + + + + + + + + + + + + @@ -1222,14 +2910,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1238,6 +2968,12 @@ + + + + + + @@ -1245,6 +2981,18 @@ + + + + + + + + + + + + @@ -1267,6 +3015,9 @@ + + + @@ -1284,6 +3035,21 @@ + + + + + + + + + + + + + + + @@ -1293,6 +3059,13 @@ + + + + + + + @@ -1300,6 +3073,12 @@ + + + + + + @@ -1310,10 +3089,28 @@ + + + + + + + + + + + + + + + + + + @@ -1324,20 +3121,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1351,16 +3178,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -1370,6 +3221,9 @@ + + + @@ -1411,8 +3265,8 @@ - - + + @@ -1429,33 +3283,260 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1465,12 +3546,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1478,6 +3622,16 @@ + + + + + + + + + + @@ -1489,16 +3643,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1507,11 +3686,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -1524,19 +3727,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1546,24 +3837,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1681,9 +4145,6 @@ - - - @@ -1695,11 +4156,12 @@ + + + - - @@ -1723,6 +4185,9 @@ + + + @@ -1736,6 +4201,12 @@ + + + + + + @@ -1769,9 +4240,17 @@ + + + + + + + + @@ -1781,9 +4260,11 @@ - - + + + + @@ -1797,6 +4278,18 @@ + + + + + + + + + + + + @@ -1808,6 +4301,9 @@ + + + @@ -1818,11 +4314,17 @@ - + + + + + + + @@ -1831,11 +4333,24 @@ + + + + + + + + + + + + + @@ -1853,6 +4368,13 @@ + + + + + + + @@ -1884,6 +4406,14 @@ + + + + + + + + @@ -1917,9 +4447,26 @@ + + + + + + + + + + + + + + + + + @@ -1937,13 +4484,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1958,6 +4645,12 @@ + + + + + + @@ -1987,6 +4680,20 @@ + + + + + + + + + + + + + + @@ -2012,6 +4719,12 @@ + + + + + + @@ -2043,6 +4756,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2064,10 +4803,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2083,12 +4866,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2142,6 +4953,9 @@ + + + @@ -2159,7 +4973,19 @@ + + + + + + + + + + + + @@ -2171,6 +4997,28 @@ + + + + + + + + + + + + + + + + + + + + + + @@ -2179,12 +5027,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2218,23 +5097,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2242,18 +5163,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -2266,11 +5211,34 @@ + + + + + + + + + + + + + + + + + + + + + + + @@ -2280,6 +5248,15 @@ + + + + + + + + + @@ -2287,10 +5264,25 @@ + + + + + + + + + + + + + + + @@ -2298,10 +5290,28 @@ + + + + + + + + + + + + + + + + + + @@ -2309,12 +5319,24 @@ + + + + + + + + + + + + @@ -2322,6 +5344,12 @@ + + + + + + @@ -2329,11 +5357,45 @@ + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2342,37 +5404,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2382,10 +5603,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -2393,32 +5638,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2427,10 +5740,24 @@ + + + + + + + + + + + + + + @@ -2446,6 +5773,7 @@ + @@ -2460,6 +5788,8 @@ + + @@ -2473,6 +5803,11 @@ + + + + + @@ -2480,6 +5815,17 @@ + + + + + + + + + + + @@ -2487,10 +5833,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2498,11 +6005,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2519,11 +6057,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2531,6 +6109,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2587,67 +6206,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2656,9 +6217,18 @@ + + + + + + + + + @@ -2666,6 +6236,15 @@ + + + + + + + + + @@ -2686,6 +6265,19 @@ + + + + + + + + + + + + + @@ -2702,9 +6294,14 @@ + + + + + @@ -2815,9 +6412,17 @@ + + + + + + + + @@ -2827,18 +6432,43 @@ + + + + + + + + + + + + + + + - - + + + + + + + + + + + + @@ -2846,9 +6476,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2862,32 +6527,60 @@ + + + + + + + + - - + + + + + + + + + + + + + - + + + + + + + + + + - + @@ -2896,33 +6589,61 @@ + + + + + + - + + + + + + + + - - - - + + + + + + + + + + + + + + + - + + + + + @@ -2932,14 +6653,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2960,6 +6718,12 @@ + + + + + + @@ -2967,23 +6731,18 @@ - - - + + + - - - - - + - @@ -3001,24 +6760,53 @@ - + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + - + + + + @@ -3026,25 +6814,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + @@ -3059,46 +6883,207 @@ - - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + - + - + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3108,6 +7093,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3150,6 +7298,15 @@ + + + + + + + + + @@ -3169,13 +7326,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3188,6 +7434,12 @@ + + + + + + @@ -3335,6 +7587,12 @@ + + + + + + @@ -3361,18 +7619,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -3388,10 +7670,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3408,6 +7721,13 @@ + + + + + + + @@ -3426,12 +7746,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3449,41 +7798,197 @@ - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3493,171 +7998,765 @@ + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + - - - - - - - + + - - - - + + - - - - - + + - - - - + + + - - - + + - - - - - - + + - - + + - - - + + - - + + + - - + + + - - - + + + - - - + + - - - + + - - + + - - + + - - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - + + - - - + + - - + + - - + + - - + + - - + + - - - + + - - + + - - + + - - + + + + - - - + + + - - - - + + - - - + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3710,6 +8809,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3721,6 +8846,9 @@ + + + @@ -3741,16 +8869,37 @@ + + + + + + + + + + + + + + + + + + + + + @@ -3760,6 +8909,9 @@ + + + @@ -3787,6 +8939,13 @@ + + + + + + + @@ -3823,6 +8982,13 @@ + + + + + + + @@ -3892,6 +9058,7 @@ + @@ -3905,8 +9072,6 @@ - - @@ -3921,6 +9086,7 @@ + @@ -4041,6 +9207,9 @@ + + + @@ -4076,6 +9245,23 @@ + + + + + + + + + + + + + + + + + @@ -4088,9 +9274,28 @@ + + + + + + + + + + + + + + + + + + + @@ -4098,6 +9303,9 @@ + + + @@ -4105,10 +9313,23 @@ + + + + + + + + + + + + + @@ -4123,9 +9344,18 @@ + + + + + + + + + @@ -4145,15 +9375,33 @@ + + + + + + + + + + + + + + + + + + @@ -4167,6 +9415,12 @@ + + + + + + @@ -4205,16 +9459,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4233,6 +9521,9 @@ + + + @@ -4240,11 +9531,22 @@ + + + + + + + + + + + @@ -4257,6 +9559,10 @@ + + + + @@ -4306,6 +9612,10 @@ + + + + @@ -4332,6 +9642,8 @@ + + @@ -4340,6 +9652,11 @@ + + + + + @@ -4350,6 +9667,8 @@ + + @@ -4362,12 +9681,25 @@ + + + + + + + + + + + + + @@ -4378,10 +9710,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4432,6 +9901,9 @@ + + + @@ -4482,6 +9954,18 @@ + + + + + + + + + + + + @@ -4494,6 +9978,9 @@ + + + @@ -4503,6 +9990,9 @@ + + + @@ -4520,6 +10010,9 @@ + + + @@ -4558,6 +10051,12 @@ + + + + + + @@ -4599,6 +10098,12 @@ + + + + + + @@ -4618,9 +10123,27 @@ + + + + + + + + + + + + + + + + + + @@ -4727,25 +10250,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4754,6 +10342,22 @@ + + + + + + + + + + + + + + + + @@ -4925,6 +10529,13 @@ + + + + + + + @@ -4938,6 +10549,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -5027,8 +10678,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/world/styller.otbm b/data/world/styller.otbm index b4a75fe..5430b7b 100644 Binary files a/data/world/styller.otbm and b/data/world/styller.otbm differ diff --git a/key.pem b/key.pem new file mode 100644 index 0000000..ec19bb4 --- /dev/null +++ b/key.pem @@ -0,0 +1,13 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCbZGkDtFsHrJVlaNhzU71xZROd15QHA7A+bdB5OZZhtKg3qmBWHXzLlFL6AIBZ +SQmIKrW8pYoaGzX4sQWbcrEhJhHGFSrT27PPvuetwUKnXT11lxUJwyHFwkpb1R/UYPAbThW+sN4Z +MFKKXT8VwePL9cQB1nd+EKyqsz2+jVt/9QIDAQABAoGAQovTtTRtr3GnYRBvcaQxAvjIV9ZUnFRm +C7Y3i1KwJhOZ3ozmSLrEEOLqTgoc7R+sJ1YzEiDKbbete11EC3gohlhW56ptj0WDf+7ptKOgqiEy +Kh4qt1sYJeeGz4GiiooJoeKFGdtk/5uvMR6FDCv6H7ewigVswzf330Q3Ya7+jYECQQERBxsga6+5 +x6IofXyNF6QuMqvuiN/pUgaStUOdlnWBf/T4yUpKvNS1+I4iDzqGWOOSR6RsaYPYVhj9iRABoKyx +AkEAkbNzB6vhLAWht4dUdGzaREF3p4SwNcu5bJRa/9wCLSHaS9JaTq4lljgVPp1zyXyJCSCWpFnl +0WvK3Qf6nVBIhQJBANS7rK8+ONWQbxENdZaZ7Rrx8HUTwSOS/fwhsGWBbl1Qzhdq/6/sIfEHkfeH +1hoH+IlpuPuf21MdAqvJt+cMwoECQF1LyBOYduYGcSgg6u5mKVldhm3pJCA+ZGxnjuGZEnet3qeA +eb05++112fyvO85ABUun524z9lokKNFh45NKLjUCQGshzV43P+RioiBhtEpB/QFzijiS4L2HKNu1 +tdhudnUjWkaf6jJmQS/ppln0hhRMHlk9Vus/bPx7LtuDuo6VQDo= +-----END RSA PRIVATE KEY----- diff --git a/schema.sql b/schema.sql index 251a4a0..9d8f4f5 100644 --- a/schema.sql +++ b/schema.sql @@ -1,72 +1,77 @@ CREATE TABLE IF NOT EXISTS `accounts` ( - `id` int(11) NOT NULL AUTO_INCREMENT, + `id` int NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL, `password` char(40) NOT NULL, - `type` int(11) NOT NULL DEFAULT '1', - `premdays` int(11) NOT NULL DEFAULT '0', - `lastday` int(10) unsigned NOT NULL DEFAULT '0', + `secret` char(16) DEFAULT NULL, + `type` int NOT NULL DEFAULT '1', + `premium_ends_at` int unsigned NOT NULL DEFAULT '0', `email` varchar(255) NOT NULL DEFAULT '', - `creation` int(11) NOT NULL DEFAULT '0', + `creation` int NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`) ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `players` ( - `id` int(11) NOT NULL AUTO_INCREMENT, + `id` int NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, - `group_id` int(11) NOT NULL DEFAULT '1', - `account_id` int(11) NOT NULL DEFAULT '0', - `level` int(11) NOT NULL DEFAULT '1', - `vocation` int(11) NOT NULL DEFAULT '0', - `health` int(11) NOT NULL DEFAULT '150', - `healthmax` int(11) NOT NULL DEFAULT '150', - `experience` bigint(20) NOT NULL DEFAULT '0', - `lookbody` int(11) NOT NULL DEFAULT '0', - `lookfeet` int(11) NOT NULL DEFAULT '0', - `lookhead` int(11) NOT NULL DEFAULT '0', - `looklegs` int(11) NOT NULL DEFAULT '0', - `looktype` int(11) NOT NULL DEFAULT '136', - `lookaddons` int(11) NOT NULL DEFAULT '0', - `direction` tinyint(1) unsigned NOT NULL DEFAULT '2', - `maglevel` int(11) NOT NULL DEFAULT '0', - `mana` int(11) NOT NULL DEFAULT '0', - `manamax` int(11) NOT NULL DEFAULT '0', - `manaspent` int(11) unsigned NOT NULL DEFAULT '0', - `soul` int(10) unsigned NOT NULL DEFAULT '0', - `town_id` int(11) NOT NULL DEFAULT '1', - `posx` int(11) NOT NULL DEFAULT '0', - `posy` int(11) NOT NULL DEFAULT '0', - `posz` int(11) NOT NULL DEFAULT '0', - `conditions` blob NOT NULL, - `cap` int(11) NOT NULL DEFAULT '400', - `sex` int(11) NOT NULL DEFAULT '0', - `lastlogin` bigint(20) unsigned NOT NULL DEFAULT '0', - `lastip` int(10) unsigned NOT NULL DEFAULT '0', - `save` tinyint(1) NOT NULL DEFAULT '1', - `skull` tinyint(1) NOT NULL DEFAULT '0', - `skulltime` bigint(20) NOT NULL DEFAULT '0', - `lastlogout` bigint(20) unsigned NOT NULL DEFAULT '0', - `blessings` tinyint(2) NOT NULL DEFAULT '0', - `onlinetime` int(11) NOT NULL DEFAULT '0', - `deletion` bigint(15) NOT NULL DEFAULT '0', - `balance` bigint(20) unsigned NOT NULL DEFAULT '0', - `offlinetraining_time` smallint(5) unsigned NOT NULL DEFAULT '43200', - `offlinetraining_skill` int(11) NOT NULL DEFAULT '-1', - `stamina` smallint(5) unsigned NOT NULL DEFAULT '2520', - `skill_fist` int(10) unsigned NOT NULL DEFAULT 10, - `skill_fist_tries` bigint(20) unsigned NOT NULL DEFAULT 0, - `skill_club` int(10) unsigned NOT NULL DEFAULT 10, - `skill_club_tries` bigint(20) unsigned NOT NULL DEFAULT 0, - `skill_sword` int(10) unsigned NOT NULL DEFAULT 10, - `skill_sword_tries` bigint(20) unsigned NOT NULL DEFAULT 0, - `skill_axe` int(10) unsigned NOT NULL DEFAULT 10, - `skill_axe_tries` bigint(20) unsigned NOT NULL DEFAULT 0, - `skill_dist` int(10) unsigned NOT NULL DEFAULT 10, - `skill_dist_tries` bigint(20) unsigned NOT NULL DEFAULT 0, - `skill_shielding` int(10) unsigned NOT NULL DEFAULT 10, - `skill_shielding_tries` bigint(20) unsigned NOT NULL DEFAULT 0, - `skill_fishing` int(10) unsigned NOT NULL DEFAULT 10, - `skill_fishing_tries` bigint(20) unsigned NOT NULL DEFAULT 0, + `group_id` int NOT NULL DEFAULT '1', + `account_id` int NOT NULL DEFAULT '0', + `level` int NOT NULL DEFAULT '1', + `vocation` int NOT NULL DEFAULT '0', + `health` int NOT NULL DEFAULT '150', + `healthmax` int NOT NULL DEFAULT '150', + `experience` bigint unsigned NOT NULL DEFAULT '0', + `lookbody` int NOT NULL DEFAULT '0', + `lookfeet` int NOT NULL DEFAULT '0', + `lookhead` int NOT NULL DEFAULT '0', + `looklegs` int NOT NULL DEFAULT '0', + `looktype` int NOT NULL DEFAULT '136', + `lookaddons` int NOT NULL DEFAULT '0', + `lookmount` int NOT NULL DEFAULT '0', + `lookmounthead` int NOT NULL DEFAULT '0', + `lookmountbody` int NOT NULL DEFAULT '0', + `lookmountlegs` int NOT NULL DEFAULT '0', + `lookmountfeet` int NOT NULL DEFAULT '0', + `direction` tinyint unsigned NOT NULL DEFAULT '2', + `maglevel` int NOT NULL DEFAULT '0', + `mana` int NOT NULL DEFAULT '0', + `manamax` int NOT NULL DEFAULT '0', + `manaspent` bigint unsigned NOT NULL DEFAULT '0', + `soul` int unsigned NOT NULL DEFAULT '0', + `town_id` int NOT NULL DEFAULT '1', + `posx` int NOT NULL DEFAULT '0', + `posy` int NOT NULL DEFAULT '0', + `posz` int NOT NULL DEFAULT '0', + `conditions` blob DEFAULT NULL, + `cap` int NOT NULL DEFAULT '400', + `sex` int NOT NULL DEFAULT '0', + `lastlogin` bigint unsigned NOT NULL DEFAULT '0', + `lastip` int unsigned NOT NULL DEFAULT '0', + `save` tinyint NOT NULL DEFAULT '1', + `skull` tinyint NOT NULL DEFAULT '0', + `skulltime` bigint NOT NULL DEFAULT '0', + `lastlogout` bigint unsigned NOT NULL DEFAULT '0', + `blessings` tinyint NOT NULL DEFAULT '0', + `onlinetime` bigint NOT NULL DEFAULT '0', + `deletion` bigint NOT NULL DEFAULT '0', + `balance` bigint unsigned NOT NULL DEFAULT '0', + `offlinetraining_time` smallint unsigned NOT NULL DEFAULT '43200', + `offlinetraining_skill` int NOT NULL DEFAULT '-1', + `stamina` smallint unsigned NOT NULL DEFAULT '2520', + `skill_fist` int unsigned NOT NULL DEFAULT 10, + `skill_fist_tries` bigint unsigned NOT NULL DEFAULT 0, + `skill_club` int unsigned NOT NULL DEFAULT 10, + `skill_club_tries` bigint unsigned NOT NULL DEFAULT 0, + `skill_sword` int unsigned NOT NULL DEFAULT 10, + `skill_sword_tries` bigint unsigned NOT NULL DEFAULT 0, + `skill_axe` int unsigned NOT NULL DEFAULT 10, + `skill_axe_tries` bigint unsigned NOT NULL DEFAULT 0, + `skill_dist` int unsigned NOT NULL DEFAULT 10, + `skill_dist_tries` bigint unsigned NOT NULL DEFAULT 0, + `skill_shielding` int unsigned NOT NULL DEFAULT 10, + `skill_shielding_tries` bigint unsigned NOT NULL DEFAULT 0, + `skill_fishing` int unsigned NOT NULL DEFAULT 10, + `skill_fishing_tries` bigint unsigned NOT NULL DEFAULT 0, PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`), FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE, @@ -74,64 +79,72 @@ CREATE TABLE IF NOT EXISTS `players` ( ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `account_bans` ( - `account_id` int(11) NOT NULL, + `account_id` int NOT NULL, `reason` varchar(255) NOT NULL, - `banned_at` bigint(20) NOT NULL, - `expires_at` bigint(20) NOT NULL, - `banned_by` int(11) NOT NULL, + `banned_at` bigint NOT NULL, + `expires_at` bigint NOT NULL, + `banned_by` int NOT NULL, PRIMARY KEY (`account_id`), FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (`banned_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `account_ban_history` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `account_id` int(11) NOT NULL, + `id` int unsigned NOT NULL AUTO_INCREMENT, + `account_id` int NOT NULL, `reason` varchar(255) NOT NULL, - `banned_at` bigint(20) NOT NULL, - `expired_at` bigint(20) NOT NULL, - `banned_by` int(11) NOT NULL, + `banned_at` bigint NOT NULL, + `expired_at` bigint NOT NULL, + `banned_by` int NOT NULL, PRIMARY KEY (`id`), FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (`banned_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; +CREATE TABLE IF NOT EXISTS `account_storage` ( + `account_id` int NOT NULL, + `key` int unsigned NOT NULL, + `value` int NOT NULL, + PRIMARY KEY (`account_id`, `key`), + FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; + CREATE TABLE IF NOT EXISTS `ip_bans` ( - `ip` int(10) unsigned NOT NULL, + `ip` int unsigned NOT NULL, `reason` varchar(255) NOT NULL, - `banned_at` bigint(20) NOT NULL, - `expires_at` bigint(20) NOT NULL, - `banned_by` int(11) NOT NULL, + `banned_at` bigint NOT NULL, + `expires_at` bigint NOT NULL, + `banned_by` int NOT NULL, PRIMARY KEY (`ip`), FOREIGN KEY (`banned_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `player_namelocks` ( - `player_id` int(11) NOT NULL, + `player_id` int NOT NULL, `reason` varchar(255) NOT NULL, - `namelocked_at` bigint(20) NOT NULL, - `namelocked_by` int(11) NOT NULL, + `namelocked_at` bigint NOT NULL, + `namelocked_by` int NOT NULL, PRIMARY KEY (`player_id`), FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (`namelocked_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `account_viplist` ( - `account_id` int(11) NOT NULL COMMENT 'id of account whose viplist entry it is', - `player_id` int(11) NOT NULL COMMENT 'id of target player of viplist entry', + `account_id` int NOT NULL COMMENT 'id of account whose viplist entry it is', + `player_id` int NOT NULL COMMENT 'id of target player of viplist entry', `description` varchar(128) NOT NULL DEFAULT '', - `icon` tinyint(2) unsigned NOT NULL DEFAULT '0', - `notify` tinyint(1) NOT NULL DEFAULT '0', + `icon` tinyint unsigned NOT NULL DEFAULT '0', + `notify` tinyint NOT NULL DEFAULT '0', UNIQUE KEY `account_player_index` (`account_id`,`player_id`), FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE, FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `guilds` ( - `id` int(11) NOT NULL AUTO_INCREMENT, + `id` int NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, - `ownerid` int(11) NOT NULL, - `creationdata` int(11) NOT NULL, + `ownerid` int NOT NULL, + `creationdata` int NOT NULL, `motd` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY (`name`), @@ -140,26 +153,26 @@ CREATE TABLE IF NOT EXISTS `guilds` ( ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `guild_invites` ( - `player_id` int(11) NOT NULL DEFAULT '0', - `guild_id` int(11) NOT NULL DEFAULT '0', + `player_id` int NOT NULL DEFAULT '0', + `guild_id` int NOT NULL DEFAULT '0', PRIMARY KEY (`player_id`,`guild_id`), FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE, FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `guild_ranks` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `guild_id` int(11) NOT NULL COMMENT 'guild', + `id` int NOT NULL AUTO_INCREMENT, + `guild_id` int NOT NULL COMMENT 'guild', `name` varchar(255) NOT NULL COMMENT 'rank name', - `level` int(11) NOT NULL COMMENT 'rank level - leader, vice, member, maybe something else', + `level` int NOT NULL COMMENT 'rank level - leader, vice, member, maybe something else', PRIMARY KEY (`id`), FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `guild_membership` ( - `player_id` int(11) NOT NULL, - `guild_id` int(11) NOT NULL, - `rank_id` int(11) NOT NULL, + `player_id` int NOT NULL, + `guild_id` int NOT NULL, + `rank_id` int NOT NULL, `nick` varchar(15) NOT NULL DEFAULT '', PRIMARY KEY (`player_id`), FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, @@ -168,81 +181,81 @@ CREATE TABLE IF NOT EXISTS `guild_membership` ( ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `guild_wars` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `guild1` int(11) NOT NULL DEFAULT '0', - `guild2` int(11) NOT NULL DEFAULT '0', + `id` int NOT NULL AUTO_INCREMENT, + `guild1` int NOT NULL DEFAULT '0', + `guild2` int NOT NULL DEFAULT '0', `name1` varchar(255) NOT NULL, `name2` varchar(255) NOT NULL, - `status` tinyint(2) NOT NULL DEFAULT '0', - `started` bigint(15) NOT NULL DEFAULT '0', - `ended` bigint(15) NOT NULL DEFAULT '0', + `status` tinyint NOT NULL DEFAULT '0', + `started` bigint NOT NULL DEFAULT '0', + `ended` bigint NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `guild1` (`guild1`), KEY `guild2` (`guild2`) ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `guildwar_kills` ( - `id` int(11) NOT NULL AUTO_INCREMENT, + `id` int NOT NULL AUTO_INCREMENT, `killer` varchar(50) NOT NULL, `target` varchar(50) NOT NULL, - `killerguild` int(11) NOT NULL DEFAULT '0', - `targetguild` int(11) NOT NULL DEFAULT '0', - `warid` int(11) NOT NULL DEFAULT '0', - `time` bigint(15) NOT NULL, + `killerguild` int NOT NULL DEFAULT '0', + `targetguild` int NOT NULL DEFAULT '0', + `warid` int NOT NULL DEFAULT '0', + `time` bigint NOT NULL, PRIMARY KEY (`id`), FOREIGN KEY (`warid`) REFERENCES `guild_wars` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `houses` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `owner` int(11) NOT NULL, - `paid` int(10) unsigned NOT NULL DEFAULT '0', - `warnings` int(11) NOT NULL DEFAULT '0', + `id` int NOT NULL AUTO_INCREMENT, + `owner` int NOT NULL, + `paid` int unsigned NOT NULL DEFAULT '0', + `warnings` int NOT NULL DEFAULT '0', `name` varchar(255) NOT NULL, - `rent` int(11) NOT NULL DEFAULT '0', - `town_id` int(11) NOT NULL DEFAULT '0', - `bid` int(11) NOT NULL DEFAULT '0', - `bid_end` int(11) NOT NULL DEFAULT '0', - `last_bid` int(11) NOT NULL DEFAULT '0', - `highest_bidder` int(11) NOT NULL DEFAULT '0', - `size` int(11) NOT NULL DEFAULT '0', - `beds` int(11) NOT NULL DEFAULT '0', + `rent` int NOT NULL DEFAULT '0', + `town_id` int NOT NULL DEFAULT '0', + `bid` int NOT NULL DEFAULT '0', + `bid_end` int NOT NULL DEFAULT '0', + `last_bid` int NOT NULL DEFAULT '0', + `highest_bidder` int NOT NULL DEFAULT '0', + `size` int NOT NULL DEFAULT '0', + `beds` int NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `owner` (`owner`), KEY `town_id` (`town_id`) ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `house_lists` ( - `house_id` int(11) NOT NULL, - `listid` int(11) NOT NULL, + `house_id` int NOT NULL, + `listid` int NOT NULL, `list` text NOT NULL, FOREIGN KEY (`house_id`) REFERENCES `houses` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `market_history` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `player_id` int(11) NOT NULL, - `sale` tinyint(1) NOT NULL DEFAULT '0', - `itemtype` int(10) unsigned NOT NULL, - `amount` smallint(5) unsigned NOT NULL, - `price` int(10) unsigned NOT NULL DEFAULT '0', - `expires_at` bigint(20) unsigned NOT NULL, - `inserted` bigint(20) unsigned NOT NULL, - `state` tinyint(1) unsigned NOT NULL, + `id` int unsigned NOT NULL AUTO_INCREMENT, + `player_id` int NOT NULL, + `sale` tinyint NOT NULL DEFAULT '0', + `itemtype` smallint unsigned NOT NULL, + `amount` smallint unsigned NOT NULL, + `price` int unsigned NOT NULL DEFAULT '0', + `expires_at` bigint unsigned NOT NULL, + `inserted` bigint unsigned NOT NULL, + `state` tinyint unsigned NOT NULL, PRIMARY KEY (`id`), KEY `player_id` (`player_id`, `sale`), FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `market_offers` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `player_id` int(11) NOT NULL, - `sale` tinyint(1) NOT NULL DEFAULT '0', - `itemtype` int(10) unsigned NOT NULL, - `amount` smallint(5) unsigned NOT NULL, - `created` bigint(20) unsigned NOT NULL, - `anonymous` tinyint(1) NOT NULL DEFAULT '0', - `price` int(10) unsigned NOT NULL DEFAULT '0', + `id` int unsigned NOT NULL AUTO_INCREMENT, + `player_id` int NOT NULL, + `sale` tinyint NOT NULL DEFAULT '0', + `itemtype` smallint unsigned NOT NULL, + `amount` smallint unsigned NOT NULL, + `created` bigint unsigned NOT NULL, + `anonymous` tinyint NOT NULL DEFAULT '0', + `price` int unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `sale` (`sale`,`itemtype`), KEY `created` (`created`), @@ -250,68 +263,90 @@ CREATE TABLE IF NOT EXISTS `market_offers` ( ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `players_online` ( - `player_id` int(11) NOT NULL, + `player_id` int NOT NULL, PRIMARY KEY (`player_id`) ) ENGINE=MEMORY DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `player_deaths` ( - `player_id` int(11) NOT NULL, - `time` bigint(20) unsigned NOT NULL DEFAULT '0', - `level` int(11) NOT NULL DEFAULT '1', + `player_id` int NOT NULL, + `time` bigint unsigned NOT NULL DEFAULT '0', + `level` int NOT NULL DEFAULT '1', `killed_by` varchar(255) NOT NULL, - `is_player` tinyint(1) NOT NULL DEFAULT '1', + `is_player` tinyint NOT NULL DEFAULT '1', `mostdamage_by` varchar(100) NOT NULL, - `mostdamage_is_player` tinyint(1) NOT NULL DEFAULT '0', - `unjustified` tinyint(1) NOT NULL DEFAULT '0', - `mostdamage_unjustified` tinyint(1) NOT NULL DEFAULT '0', + `mostdamage_is_player` tinyint NOT NULL DEFAULT '0', + `unjustified` tinyint NOT NULL DEFAULT '0', + `mostdamage_unjustified` tinyint NOT NULL DEFAULT '0', FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE, KEY `killed_by` (`killed_by`), KEY `mostdamage_by` (`mostdamage_by`) ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `player_depotitems` ( - `player_id` int(11) NOT NULL, - `sid` int(11) NOT NULL COMMENT 'any given range eg 0-100 will be reserved for depot lockers and all > 100 will be then normal items inside depots', - `pid` int(11) NOT NULL DEFAULT '0', - `itemtype` smallint(6) NOT NULL, - `count` smallint(5) NOT NULL DEFAULT '0', + `player_id` int NOT NULL, + `sid` int NOT NULL COMMENT 'any given range eg 0-100 will be reserved for depot lockers and all > 100 will be then normal items inside depots', + `pid` int NOT NULL DEFAULT '0', + `itemtype` smallint unsigned NOT NULL, + `count` smallint NOT NULL DEFAULT '0', `attributes` blob NOT NULL, UNIQUE KEY `player_id_2` (`player_id`, `sid`), FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `player_inboxitems` ( - `player_id` int(11) NOT NULL, - `sid` int(11) NOT NULL, - `pid` int(11) NOT NULL DEFAULT '0', - `itemtype` smallint(6) NOT NULL, - `count` smallint(5) NOT NULL DEFAULT '0', + `player_id` int NOT NULL, + `sid` int NOT NULL, + `pid` int NOT NULL DEFAULT '0', + `itemtype` smallint unsigned NOT NULL, + `count` smallint NOT NULL DEFAULT '0', + `attributes` blob NOT NULL, + UNIQUE KEY `player_id_2` (`player_id`, `sid`), + FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; + +CREATE TABLE IF NOT EXISTS `player_storeinboxitems` ( + `player_id` int NOT NULL, + `sid` int NOT NULL, + `pid` int NOT NULL DEFAULT '0', + `itemtype` smallint unsigned NOT NULL, + `count` smallint NOT NULL DEFAULT '0', + `attributes` blob NOT NULL, + UNIQUE KEY `player_id_2` (`player_id`, `sid`), + FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; + +CREATE TABLE IF NOT EXISTS `player_depotlockeritems` ( + `player_id` int NOT NULL, + `sid` int NOT NULL COMMENT 'any given range eg 0-100 will be reserved for depot lockers and all > 100 will be then normal items inside depots', + `pid` int NOT NULL DEFAULT '0', + `itemtype` smallint NOT NULL, + `count` smallint NOT NULL DEFAULT '0', `attributes` blob NOT NULL, UNIQUE KEY `player_id_2` (`player_id`, `sid`), FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `player_items` ( - `player_id` int(11) NOT NULL DEFAULT '0', - `pid` int(11) NOT NULL DEFAULT '0', - `sid` int(11) NOT NULL DEFAULT '0', - `itemtype` smallint(6) NOT NULL DEFAULT '0', - `count` smallint(5) NOT NULL DEFAULT '0', + `player_id` int NOT NULL DEFAULT '0', + `pid` int NOT NULL DEFAULT '0', + `sid` int NOT NULL DEFAULT '0', + `itemtype` smallint unsigned NOT NULL DEFAULT '0', + `count` smallint NOT NULL DEFAULT '0', `attributes` blob NOT NULL, FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE, KEY `sid` (`sid`) ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `player_spells` ( - `player_id` int(11) NOT NULL, + `player_id` int NOT NULL, `name` varchar(255) NOT NULL, FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `player_storage` ( - `player_id` int(11) NOT NULL DEFAULT '0', - `key` int(10) unsigned NOT NULL DEFAULT '0', - `value` int(11) NOT NULL DEFAULT '0', + `player_id` int NOT NULL DEFAULT '0', + `key` int unsigned NOT NULL DEFAULT '0', + `value` int NOT NULL DEFAULT '0', PRIMARY KEY (`player_id`,`key`), FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; @@ -323,34 +358,22 @@ CREATE TABLE IF NOT EXISTS `server_config` ( ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `tile_store` ( - `house_id` int(11) NOT NULL, + `house_id` int NOT NULL, `data` longblob NOT NULL, FOREIGN KEY (`house_id`) REFERENCES `houses` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; CREATE TABLE IF NOT EXISTS `towns` ( - `id` int(11) NOT NULL AUTO_INCREMENT, + `id` int NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, - `posx` int(11) NOT NULL DEFAULT '0', - `posy` int(11) NOT NULL DEFAULT '0', - `posz` int(11) NOT NULL DEFAULT '0', + `posx` int NOT NULL DEFAULT '0', + `posy` int NOT NULL DEFAULT '0', + `posz` int NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`) ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; -INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '18'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0'); - -CREATE TABLE `auction_system` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `player_id` int(11) NOT NULL, - `item_name` varchar(255) NOT NULL, - `item_id` smallint(6) NOT NULL, - `count` smallint(5) NOT NULL, - `value` int(7) NOT NULL, - `date` bigint(20) NOT NULL, - PRIMARY KEY (`id`), - FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; +INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '30'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0'); DROP TRIGGER IF EXISTS `ondelete_players`; DROP TRIGGER IF EXISTS `oncreate_guilds`;